diff --git a/CLAUDE.md b/CLAUDE.md index d25bf3b..ac78fec 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,7 +26,7 @@ cargo run --bin fastedge-run -- http --port 8080 --wasm ./my_app.wasm ## Repository setup gotchas - **WIT is vendored via git subtree.** `crates/reactor/wit/` contains the `FastEdge-wit` sources directly, so no `git submodule update` step is required after clone or branch switches. To sync it with upstream, run `git subtree pull --prefix=crates/reactor/wit https://github.com/G-Core/FastEdge-wit.git main --squash`. -- **Custom Wasmtime fork.** All `wasmtime-*` deps point at `github.com/G-Core/wasmtime.git#release-36.0.0`. `.cargo/config.toml` contains a commented-out `[patch]` block that redirects those to a local sibling `../wasmtime` checkout — uncomment when developing against a local Wasmtime tree. +- **Custom Wasmtime fork.** All `wasmtime-*` deps point at `github.com/G-Core/wasmtime.git#release-48.0.0`. The repo-root `.cargo/config.toml` carries a `[patch]` block redirecting `wasmtime`, `wasmtime-wasi`, `wasmtime-wasi-nn` and `wasmtime-wasi-http` to a local sibling `../wasmtime` checkout; `wasmtime-wasi-io` and `wasmtime-environ` are not patched, so a local tree must stay in sync with the pushed branch. - **Wasm components vs core modules.** `componentize_if_necessary` (in `crates/runtime/src/lib.rs`) auto-wraps core modules using the bundled Preview1 adapter (`crates/runtime/src/adapters/wasi_snapshot_preview1.reactor.wasm`). Don't replace that adapter casually — it's pinned to the Wasmtime fork. ## Architecture @@ -71,7 +71,7 @@ Every Wasmtime `Store` holds a `Data` (in `crates/runtime/src/lib.rs`). It bu ## Crate features worth knowing -- `runtime`: `metrics` opts into Prometheus + lazy_static and propagates through `http-service/metrics`. +- `runtime`: no metrics backend is compiled in. App-call outcomes are reported through the `runtime::util::metrics` sink (`set_sink`), and live-instance counts through the plain atomics in `runtime::instances` (`live` / `flush_peak`); embedders (e.g. the FastEdge server) export them however they like. With no sink installed, reporting is a no-op. - `key-value-store`: `redis` enables the Redis-backed implementation. `runtime` already turns this on; if you depend on `key-value-store` from elsewhere, opt in explicitly. - `cache`: no features yet — backend choice is at runtime via the `Arc` injected into `Data`. diff --git a/Cargo.lock b/Cargo.lock index d70c142..fa5f7b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,29 +2,20 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli 0.32.3", -] - [[package]] name = "addr2line" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59317f77929f0e679d39364702289274de2f0f0b22cbf50b2b8cff2169a0b27a" dependencies = [ - "gimli 0.33.0", + "gimli", ] [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -43,9 +34,9 @@ checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -86,7 +77,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -97,7 +88,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -108,9 +99,9 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arbitrary" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed" dependencies = [ "derive_arbitrary", ] @@ -143,13 +134,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] @@ -202,9 +193,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "block-buffer" @@ -217,9 +208,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" dependencies = [ "bytes", "cfg_aliases", @@ -245,13 +236,13 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -268,9 +259,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bytesize" -version = "2.4.2" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7c8918969267b2932ffd5655509bbbea0833823058c378876953217f5fc50e" +checksum = "7354288c522e7e980fafd2075d63d1285794c3a6a16cdd492f189ea406e5f18b" [[package]] name = "cache" @@ -282,7 +273,7 @@ dependencies = [ "smol_str", "tokio", "tracing", - "wasmtime 48.0.1", + "wasmtime", ] [[package]] @@ -341,48 +332,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "cap-fs-ext" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" -dependencies = [ - "cap-primitives 3.4.5", - "cap-std", - "io-lifetimes 2.0.4", - "windows-sys 0.52.0", -] - -[[package]] -name = "cap-net-ext" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" -dependencies = [ - "cap-primitives 3.4.5", - "cap-std", - "rustix 1.1.4", - "smallvec", -] - -[[package]] -name = "cap-primitives" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" -dependencies = [ - "ambient-authority", - "fs-set-times", - "io-extras 0.18.4", - "io-lifetimes 2.0.4", - "ipnet", - "maybe-owned", - "rustix 1.1.4", - "rustix-linux-procfs", - "windows-sys 0.52.0", - "winx", -] - [[package]] name = "cap-primitives" version = "4.0.3" @@ -391,57 +340,21 @@ checksum = "8b5f74729fd2f44701d1a8eb47e906cdb3ccd9ec0f02baad85a744b791940b18" dependencies = [ "ambient-authority", "fs-set-times", - "io-extras 0.19.0", + "io-extras", "io-lifetimes 3.0.1", "ipnet", "maybe-owned", - "rustix 1.1.4", + "rustix", "rustix-linux-procfs", - "windows-sys 0.61.2", - "winx", -] - -[[package]] -name = "cap-rand" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" -dependencies = [ - "ambient-authority", - "rand 0.8.7", -] - -[[package]] -name = "cap-std" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" -dependencies = [ - "cap-primitives 3.4.5", - "io-extras 0.18.4", - "io-lifetimes 2.0.4", - "rustix 1.1.4", -] - -[[package]] -name = "cap-time-ext" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" -dependencies = [ - "ambient-authority", - "cap-primitives 3.4.5", - "iana-time-zone", - "once_cell", - "rustix 1.1.4", + "windows-sys 0.60.2", "winx", ] [[package]] name = "cc" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" dependencies = [ "find-msvc-tools", "jobserver", @@ -451,9 +364,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cfg_aliases" @@ -494,9 +407,9 @@ checksum = "bba18ee93d577a8428902687bcc2b6b45a56b1981a1f6d779731c86cc4c5db18" [[package]] name = "clap" -version = "4.6.4" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -504,9 +417,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.2" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", @@ -516,21 +429,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.4" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] name = "clap_lex" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "cobs" @@ -538,7 +451,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.19", + "thiserror 2.0.21", ] [[package]] @@ -554,14 +467,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "combine" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", "futures-core", @@ -587,6 +500,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpp_demangle" version = "0.5.1" @@ -616,206 +535,131 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "cranelift-assembler-x64-meta 0.123.12", -] - -[[package]] -name = "cranelift-assembler-x64" -version = "0.135.1" -dependencies = [ - "cranelift-assembler-x64-meta 0.135.1", -] - -[[package]] -name = "cranelift-assembler-x64-meta" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "0.135.2" dependencies = [ - "cranelift-srcgen 0.123.12", + "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.135.1" +version = "0.135.2" dependencies = [ - "cranelift-srcgen 0.135.1", + "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "0.135.2" dependencies = [ - "cranelift-entity 0.123.12", + "cranelift-entity 0.135.2", + "wasmtime-internal-core 48.0.2", ] [[package]] name = "cranelift-bforest" -version = "0.135.1" +version = "0.135.2" +source = "git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0#ef281a08e10646e8b0592d0081752fef23cab585" dependencies = [ - "cranelift-entity 0.135.1", - "wasmtime-internal-core", + "cranelift-entity 0.135.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", + "wasmtime-internal-core 48.0.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", ] [[package]] name = "cranelift-bitset" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "0.135.2" dependencies = [ "serde", "serde_derive", + "wasmtime-internal-core 48.0.2", ] [[package]] name = "cranelift-bitset" -version = "0.135.1" +version = "0.135.2" +source = "git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0#ef281a08e10646e8b0592d0081752fef23cab585" dependencies = [ "serde", "serde_derive", - "wasmtime-internal-core", -] - -[[package]] -name = "cranelift-codegen" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "bumpalo", - "cranelift-assembler-x64 0.123.12", - "cranelift-bforest 0.123.12", - "cranelift-bitset 0.123.12", - "cranelift-codegen-meta 0.123.12", - "cranelift-codegen-shared 0.123.12", - "cranelift-control 0.123.12", - "cranelift-entity 0.123.12", - "cranelift-isle 0.123.12", - "gimli 0.32.3", - "hashbrown 0.15.5", - "log", - "pulley-interpreter 36.0.12", - "regalloc2 0.12.2", - "rustc-hash", - "serde", - "smallvec", - "target-lexicon", - "wasmtime-internal-math", + "wasmtime-internal-core 48.0.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", ] [[package]] name = "cranelift-codegen" -version = "0.135.1" +version = "0.135.2" dependencies = [ "bumpalo", - "cranelift-assembler-x64 0.135.1", - "cranelift-bforest 0.135.1", - "cranelift-bitset 0.135.1", - "cranelift-codegen-meta 0.135.1", - "cranelift-codegen-shared 0.135.1", - "cranelift-control 0.135.1", - "cranelift-entity 0.135.1", - "cranelift-isle 0.135.1", - "gimli 0.33.0", + "cranelift-assembler-x64", + "cranelift-bforest 0.135.2", + "cranelift-bitset 0.135.2", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity 0.135.2", + "cranelift-isle", + "gimli", "hashbrown 0.17.1", "libm", "log", "postcard", - "pulley-interpreter 48.0.1", - "regalloc2 0.15.2", + "pulley-interpreter", + "regalloc2", "rustc-hash", "serde", "serde_derive", "sha2", "smallvec", "target-lexicon", - "wasmtime-internal-core", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "cranelift-assembler-x64-meta 0.123.12", - "cranelift-codegen-shared 0.123.12", - "cranelift-srcgen 0.123.12", - "heck", - "pulley-interpreter 36.0.12", + "wasmtime-internal-core 48.0.2", ] [[package]] name = "cranelift-codegen-meta" -version = "0.135.1" +version = "0.135.2" dependencies = [ - "cranelift-assembler-x64-meta 0.135.1", - "cranelift-codegen-shared 0.135.1", - "cranelift-srcgen 0.135.1", + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", "heck", - "pulley-interpreter 48.0.1", + "pulley-interpreter", ] [[package]] name = "cranelift-codegen-shared" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" - -[[package]] -name = "cranelift-codegen-shared" -version = "0.135.1" - -[[package]] -name = "cranelift-control" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "arbitrary", -] +version = "0.135.2" [[package]] name = "cranelift-control" -version = "0.135.1" +version = "0.135.2" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "0.135.2" dependencies = [ - "cranelift-bitset 0.123.12", + "cranelift-bitset 0.135.2", "serde", "serde_derive", + "wasmtime-internal-core 48.0.2", ] [[package]] name = "cranelift-entity" -version = "0.135.1" +version = "0.135.2" +source = "git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0#ef281a08e10646e8b0592d0081752fef23cab585" dependencies = [ - "cranelift-bitset 0.135.1", + "cranelift-bitset 0.135.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", "serde", "serde_derive", - "wasmtime-internal-core", -] - -[[package]] -name = "cranelift-frontend" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "cranelift-codegen 0.123.12", - "log", - "smallvec", - "target-lexicon", + "wasmtime-internal-core 48.0.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", ] [[package]] name = "cranelift-frontend" -version = "0.135.1" +version = "0.135.2" dependencies = [ - "cranelift-codegen 0.135.1", + "cranelift-codegen", "hashbrown 0.17.1", "log", "smallvec", @@ -824,64 +668,44 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" - -[[package]] -name = "cranelift-isle" -version = "0.135.1" +version = "0.135.2" [[package]] name = "cranelift-native" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "0.135.2" dependencies = [ - "cranelift-codegen 0.123.12", + "cranelift-codegen", "libc", "target-lexicon", ] -[[package]] -name = "cranelift-native" -version = "0.135.1" -dependencies = [ - "cranelift-codegen 0.135.1", - "libc", - "target-lexicon", -] - -[[package]] -name = "cranelift-srcgen" -version = "0.123.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" - [[package]] name = "cranelift-srcgen" -version = "0.135.1" +version = "0.135.2" [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -889,18 +713,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crunchy" @@ -929,13 +753,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -971,13 +795,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -1008,9 +832,9 @@ checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" [[package]] name = "either" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" [[package]] name = "embedded-io" @@ -1026,11 +850,16 @@ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" dependencies = [ "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] @@ -1071,7 +900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1094,12 +923,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - [[package]] name = "fancy-regex" version = "0.13.0" @@ -1136,7 +959,7 @@ dependencies = [ "tempfile", "tokio", "utils", - "wasmtime 48.0.1", + "wasmtime", ] [[package]] @@ -1145,22 +968,11 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -[[package]] -name = "fd-lock" -version = "4.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" -dependencies = [ - "cfg-if", - "rustix 1.1.4", - "windows-sys 0.52.0", -] - [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "fixedbitset" @@ -1174,12 +986,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -1217,15 +1023,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" dependencies = [ "io-lifetimes 2.0.4", - "rustix 1.1.4", - "windows-sys 0.52.0", + "rustix", + "windows-sys 0.59.0", ] [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1238,9 +1044,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1248,15 +1054,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1265,38 +1071,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -1315,7 +1121,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "debugid", "rustc-hash", "serde", @@ -1605,17 +1411,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] - [[package]] name = "gimli" version = "0.33.0" @@ -1630,9 +1425,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.15" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" dependencies = [ "atomic-waker", "bytes", @@ -1662,23 +1457,13 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", - "serde", -] - [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "foldhash 0.2.0", + "foldhash", ] [[package]] @@ -1687,7 +1472,7 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ - "foldhash 0.2.0", + "foldhash", "serde", "serde_core", ] @@ -1700,21 +1485,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -1752,9 +1531,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -1778,7 +1557,6 @@ dependencies = [ "hyper", "hyper-util", "key-value-store", - "nanoid", "reactor", "runtime", "secret", @@ -1789,9 +1567,8 @@ dependencies = [ "tracing", "tracing-test", "utils", - "wasi-common", - "wasmtime 48.0.1", - "wasmtime-wasi 48.0.1", + "wasmtime", + "wasmtime-wasi", "wasmtime-wasi-http", "wasmtime-wasi-nn", ] @@ -1816,9 +1593,9 @@ checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" [[package]] name = "hyper" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" dependencies = [ "atomic-waker", "bytes", @@ -1854,15 +1631,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", + "httparse", "hyper", "libc", "pin-project-lite", @@ -1898,9 +1676,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -1912,9 +1690,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1925,9 +1703,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1939,16 +1717,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -1959,15 +1738,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -2007,9 +1786,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2017,16 +1796,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "io-extras" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" -dependencies = [ - "io-lifetimes 2.0.4", - "windows-sys 0.52.0", -] - [[package]] name = "io-extras" version = "0.19.0" @@ -2051,9 +1820,9 @@ checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96" [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is-terminal" @@ -2063,7 +1832,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2128,9 +1897,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -2148,14 +1917,14 @@ dependencies = [ "smol_str", "tokio", "tracing", - "wasmtime 48.0.1", + "wasmtime", ] [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "leb128" @@ -2171,9 +1940,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.189" +version = "0.2.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" [[package]] name = "libloading" @@ -2193,19 +1962,13 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" dependencies = [ "libc", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2214,9 +1977,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "lock_api" @@ -2229,18 +1992,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "mach2" -version = "0.4.3" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "mach2" @@ -2271,11 +2025,11 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memfd" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +checksum = "57804b2c9b69967f1536a56f86297e367a33b19e98852ed624b84551cdbc0d90" dependencies = [ - "rustix 1.1.4", + "rustix", ] [[package]] @@ -2299,9 +2053,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "wasi", @@ -2327,9 +2081,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.15" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -2343,13 +2097,10 @@ dependencies = [ ] [[package]] -name = "nanoid" -version = "0.5.0" +name = "multiversion_no_op" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628de41fe064cc3f0cf07f3d299ee3e73521adaff72278731d5c8cae3797873" -dependencies = [ - "rand 0.9.5", -] +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" [[package]] name = "native-tls" @@ -2388,7 +2139,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2397,7 +2148,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "num-bigint", + "num-bigint 0.4.8", "num-complex", "num-integer", "num-iter", @@ -2415,6 +2166,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -2427,9 +2188,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -2450,7 +2211,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "num-bigint", + "num-bigint 0.4.8", "num-integer", "num-traits", ] @@ -2499,31 +2260,19 @@ dependencies = [ [[package]] name = "object" -version = "0.37.3" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" dependencies = [ "crc32fast", - "hashbrown 0.15.5", + "hashbrown 0.17.1", "indexmap", "memchr", ] [[package]] -name = "object" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" -dependencies = [ - "crc32fast", - "hashbrown 0.17.1", - "indexmap", - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.4" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" @@ -2539,7 +2288,7 @@ version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "foreign-types", "libc", @@ -2688,21 +2437,21 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "13bee6c73da26345c729282832b60b0363cf3dd9f4bfd81d8551b7a1c889a113" [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "postcard" @@ -2718,9 +2467,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -2762,99 +2511,19 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "procfs" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" -dependencies = [ - "bitflags 2.13.1", - "hex", - "procfs-core", - "rustix 0.38.44", -] - -[[package]] -name = "procfs-core" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" -dependencies = [ - "bitflags 2.13.1", - "hex", -] - -[[package]] -name = "prometheus" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" -dependencies = [ - "cfg-if", - "fnv", - "lazy_static", - "libc", - "memchr", - "parking_lot", - "procfs", - "protobuf", - "thiserror 2.0.19", -] - -[[package]] -name = "protobuf" -version = "3.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" -dependencies = [ - "once_cell", - "protobuf-support", - "thiserror 1.0.69", -] - -[[package]] -name = "protobuf-support" -version = "3.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" -dependencies = [ - "thiserror 1.0.69", -] - -[[package]] -name = "pulley-interpreter" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "cranelift-bitset 0.123.12", - "log", - "pulley-macros 36.0.12", - "wasmtime-internal-math", -] - [[package]] name = "pulley-interpreter" -version = "48.0.1" +version = "48.0.2" dependencies = [ - "cranelift-bitset 0.135.1", + "cranelift-bitset 0.135.2", "log", - "pulley-macros 48.0.1", - "wasmtime-internal-core", -] - -[[package]] -name = "pulley-macros" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "pulley-macros", + "wasmtime-internal-core 48.0.2", ] [[package]] name = "pulley-macros" -version = "48.0.1" +version = "48.0.2" dependencies = [ "proc-macro2", "quote", @@ -2910,9 +2579,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2931,9 +2600,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ "chacha20", "getrandom 0.4.3", @@ -3009,7 +2678,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -3036,7 +2705,7 @@ dependencies = [ name = "reactor" version = "0.21.0" dependencies = [ - "wasmtime 48.0.1", + "wasmtime", ] [[package]] @@ -3047,9 +2716,9 @@ checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" [[package]] name = "redis" -version = "1.4.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b9503711b03773e43b31668c7b5bd279ee7cd9b7d18cff7c23a42cc1d08e5a" +checksum = "ed5b98ae99461e02895c3a96a25dbe4d852a3d5e5b815d31d5d9e4b5344d99e9" dependencies = [ "arc-swap", "arcstr", @@ -3062,7 +2731,7 @@ dependencies = [ "futures-util", "itoa", "native-tls", - "num-bigint", + "num-bigint 0.5.1", "percent-encoding", "pin-project-lite", "ryu", @@ -3081,7 +2750,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -3095,20 +2764,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "regalloc2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5216b1837de2149f8bc8e6d5f88a9326b63b8c836ed58ce4a0a29ec736a59734" -dependencies = [ - "allocator-api2", - "bumpalo", - "hashbrown 0.15.5", - "log", - "rustc-hash", - "smallvec", -] - [[package]] name = "regalloc2" version = "0.15.2" @@ -3138,9 +2793,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -3180,10 +2835,11 @@ dependencies = [ "claims", "http", "http-backend", + "http-body", + "http-body-util", "key-value-store", - "lazy_static", "moka", - "prometheus", + "rand 0.8.8", "secret", "serde", "serde_json", @@ -3192,12 +2848,11 @@ dependencies = [ "tokio-util", "tracing", "utils", - "wasi-common", - "wasmtime 48.0.1", - "wasmtime-environ 36.0.12", - "wasmtime-wasi 48.0.1", + "wasmtime", + "wasmtime-environ 48.0.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", + "wasmtime-wasi", "wasmtime-wasi-http", - "wasmtime-wasi-io 36.0.12", + "wasmtime-wasi-io 48.0.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", "wasmtime-wasi-nn", "wat", "wit-component 0.246.2", @@ -3217,30 +2872,17 @@ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustix" -version = "0.38.44" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "errno", "libc", - "linux-raw-sys 0.4.15", + "linux-raw-sys", "windows-sys 0.52.0", ] -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", -] - [[package]] name = "rustix-linux-procfs" version = "0.1.1" @@ -3248,16 +2890,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" dependencies = [ "once_cell", - "rustix 1.1.4", + "rustix", ] [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -3276,9 +2919,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -3352,7 +2995,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation", "core-foundation-sys", "libc", @@ -3412,7 +3055,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] @@ -3439,9 +3082,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" dependencies = [ "serde_core", ] @@ -3509,6 +3152,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -3517,9 +3166,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" dependencies = [ "serde", ] @@ -3541,7 +3190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3575,9 +3224,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -3595,13 +3244,24 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "sysctl" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "byteorder", "enum-as-inner", "libc", @@ -3615,7 +3275,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "byteorder", "enum-as-inner", "libc", @@ -3623,22 +3283,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "system-interface" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" -dependencies = [ - "bitflags 2.13.1", - "cap-fs-ext", - "cap-std", - "fd-lock", - "io-lifetimes 2.0.4", - "rustix 0.38.44", - "windows-sys 0.52.0", - "winx", -] - [[package]] name = "tagptr" version = "0.2.0" @@ -3660,8 +3304,8 @@ dependencies = [ "fastrand", "getrandom 0.4.3", "once_cell", - "rustix 1.1.4", - "windows-sys 0.61.2", + "rustix", + "windows-sys 0.52.0", ] [[package]] @@ -3675,34 +3319,34 @@ dependencies = [ [[package]] name = "test-case" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +checksum = "124953e7f67cb0b2fcfb87e899e4ae5a64fb68e2e160767933cc67b646fb0042" dependencies = [ "test-case-macros", ] [[package]] name = "test-case-core" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +checksum = "bd097615b407247e102046ba34426bb594923ab3554cf0f3cdfed050f1a5a3e8" dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] name = "test-case-macros" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +checksum = "f4ce35ece947bccac166e1ded639133827a953f69a48ba134d17f91108333082" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "test-case-core", ] @@ -3717,11 +3361,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.21", ] [[package]] @@ -3737,13 +3381,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] @@ -3757,9 +3401,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -3767,9 +3411,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.53.1" +version = "1.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" dependencies = [ "bytes", "libc", @@ -3784,13 +3428,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -3805,12 +3449,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -3867,39 +3510,39 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.25.17+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" dependencies = [ "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", + "toml_datetime 1.1.2+spec-1.1.0", "toml_parser", "winnow 1.0.4", ] [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ "winnow 1.0.4", ] [[package]] name = "toml_writer" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" [[package]] name = "tower" @@ -3929,7 +3572,6 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -4041,9 +3683,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "unicode-width" @@ -4096,9 +3738,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.24.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -4135,9 +3777,9 @@ dependencies = [ [[package]] name = "want" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" dependencies = [ "try-lock", ] @@ -4148,30 +3790,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi-common" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "anyhow", - "bitflags 2.13.1", - "cap-fs-ext", - "cap-rand", - "cap-std", - "cap-time-ext", - "fs-set-times", - "io-extras 0.18.4", - "io-lifetimes 2.0.4", - "log", - "rustix 1.1.4", - "system-interface", - "thiserror 2.0.19", - "tracing", - "wasmtime 36.0.12", - "wiggle 36.0.12", - "windows-sys 0.60.2", -] - [[package]] name = "wasip2" version = "1.0.4+wasi-0.2.12" @@ -4183,9 +3801,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -4196,9 +3814,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4206,31 +3824,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-compose" -version = "0.254.0" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd717357bff09b5f1ea49aea8ed1cd149b0c521823a1cc70bc2ac16b90b651a" +checksum = "4c0c1033c10d23d96f40e761ae66cc30c70cea3323a8edfe9159dac42108873f" dependencies = [ "anyhow", "heck", @@ -4238,39 +3856,39 @@ dependencies = [ "log", "petgraph", "smallvec", - "wasm-encoder 0.254.0", - "wasmparser 0.254.0", + "wasm-encoder 0.254.2", + "wasmparser 0.254.2", "wat", ] [[package]] name = "wasm-encoder" -version = "0.236.1" +version = "0.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724fccfd4f3c24b7e589d333fc0429c68042897a7e8a5f8694f31792471841e7" +checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" dependencies = [ "leb128fmt", - "wasmparser 0.236.1", + "wasmparser 0.246.2", ] [[package]] name = "wasm-encoder" -version = "0.246.2" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" +checksum = "ebaca0b540bdb15d195ff34fb23074255948708b8916a11a339ba4513dc61906" dependencies = [ "leb128fmt", - "wasmparser 0.246.2", + "wasmparser 0.254.2", ] [[package]] name = "wasm-encoder" -version = "0.254.0" +version = "0.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09480d646178e5fdd12bb06e812d0af9a3a191dbc9cd697fdc86687beade7393" +checksum = "2608e8bb6d67fd68f5a8d0eb1363d6e7bcbc1f8ded5a0bd3a1e382462b876b22" dependencies = [ "leb128fmt", - "wasmparser 0.254.0", + "wasmparser 0.261.0", ] [[package]] @@ -4287,27 +3905,14 @@ dependencies = [ [[package]] name = "wasm-metadata" -version = "0.254.0" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01df5f3b4ca7881e843f3bc0fb8a3905d79c68692250dcb8e33e698705ccdb6" +checksum = "5d1880dd9a848aa68f387966256c34ad49abb4607e3cfa35720d2723d0591cd3" dependencies = [ "anyhow", "indexmap", - "wasm-encoder 0.254.0", - "wasmparser 0.254.0", -] - -[[package]] -name = "wasmparser" -version = "0.236.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7" -dependencies = [ - "bitflags 2.13.1", - "hashbrown 0.15.5", - "indexmap", - "semver", - "serde", + "wasm-encoder 0.254.2", + "wasmparser 0.254.2", ] [[package]] @@ -4316,7 +3921,7 @@ version = "0.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "hashbrown 0.16.1", "indexmap", "semver", @@ -4324,11 +3929,11 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.254.0" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27" +checksum = "c5ff8685b587722894b6f25958fc608d315b93b8d23647bfb567664e7006d757" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "hashbrown 0.17.1", "indexmap", "semver", @@ -4336,97 +3941,51 @@ dependencies = [ ] [[package]] -name = "wasmprinter" -version = "0.236.1" +name = "wasmparser" +version = "0.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df225df06a6df15b46e3f73ca066ff92c2e023670969f7d50ce7d5e695abbb1" +checksum = "4f20f20e44f7e8aeb6744823ea9d869ede51e51be4fdaedede2852282e54d2d8" dependencies = [ - "anyhow", - "termcolor", - "wasmparser 0.236.1", + "bitflags 2.13.2", + "indexmap", + "semver", ] [[package]] name = "wasmprinter" -version = "0.254.0" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e3ba11e024f504698f87b629b2b66c22a1231758de7607754963f7d198be39" +checksum = "8b03c8f270f3f384f70f42869b5ff7f6253fe4f0aa8590f7edd332c1943e5f51" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.254.0", + "wasmparser 0.254.2", ] [[package]] name = "wasmtime" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "48.0.2" dependencies = [ - "addr2line 0.25.1", - "anyhow", + "addr2line", "async-trait", - "bitflags 2.13.1", - "bumpalo", - "cc", - "cfg-if", - "encoding_rs", - "hashbrown 0.15.5", - "indexmap", - "libc", - "log", - "mach2 0.4.3", - "memfd", - "object 0.37.3", - "once_cell", - "postcard", - "pulley-interpreter 36.0.12", - "rustix 1.1.4", - "semver", - "serde", - "serde_derive", - "smallvec", - "target-lexicon", - "wasmparser 0.236.1", - "wasmtime-environ 36.0.12", - "wasmtime-internal-asm-macros", - "wasmtime-internal-component-macro 36.0.12", - "wasmtime-internal-component-util 36.0.12", - "wasmtime-internal-cranelift 36.0.12", - "wasmtime-internal-fiber 36.0.12", - "wasmtime-internal-jit-debug 36.0.12", - "wasmtime-internal-jit-icache-coherence 36.0.12", - "wasmtime-internal-math", - "wasmtime-internal-slab", - "wasmtime-internal-unwinder 36.0.12", - "wasmtime-internal-versioned-export-macros 36.0.12", - "wasmtime-internal-winch", - "windows-sys 0.60.2", -] - -[[package]] -name = "wasmtime" -version = "48.0.1" -dependencies = [ - "addr2line 0.26.1", - "async-trait", - "bitflags 2.13.1", + "bitflags 2.13.2", "bumpalo", "cc", "encoding_rs", "futures", "fxprof-processed-profile", - "gimli 0.33.0", + "gimli", "ittapi", "libc", "log", - "mach2 0.6.0", + "mach2", "memfd", - "object 0.39.1", + "object", "once_cell", "postcard", - "pulley-interpreter 48.0.1", + "pulley-interpreter", "rayon", - "rustix 1.1.4", + "rustix", "semver", "serde", "serde_derive", @@ -4435,142 +3994,115 @@ dependencies = [ "target-lexicon", "tempfile", "wasm-compose", - "wasm-encoder 0.254.0", - "wasmparser 0.254.0", - "wasmtime-environ 48.0.1", + "wasm-encoder 0.254.2", + "wasmparser 0.254.2", + "wasmtime-environ 48.0.2", "wasmtime-internal-cache", - "wasmtime-internal-component-macro 48.0.1", - "wasmtime-internal-component-util 48.0.1", - "wasmtime-internal-core", - "wasmtime-internal-cranelift 48.0.1", - "wasmtime-internal-fiber 48.0.1", - "wasmtime-internal-jit-debug 48.0.1", - "wasmtime-internal-jit-icache-coherence 48.0.1", - "wasmtime-internal-unwinder 48.0.1", - "wasmtime-internal-versioned-export-macros 48.0.1", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-core 48.0.2", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", "wat", "windows-sys 0.61.2", - "wit-parser 0.254.0", + "wit-parser 0.254.2", ] [[package]] name = "wasmtime-environ" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "48.0.2" dependencies = [ "anyhow", - "cranelift-bitset 0.123.12", - "cranelift-entity 0.123.12", - "gimli 0.32.3", + "cpp_demangle", + "cranelift-bforest 0.135.2", + "cranelift-bitset 0.135.2", + "cranelift-entity 0.135.2", + "gimli", + "hashbrown 0.17.1", "indexmap", "log", - "object 0.37.3", + "object", "postcard", + "rustc-demangle", "semver", "serde", "serde_derive", + "sha2", "smallvec", "target-lexicon", - "wasm-encoder 0.236.1", - "wasmparser 0.236.1", - "wasmprinter 0.236.1", - "wasmtime-internal-component-util 36.0.12", + "wasm-encoder 0.254.2", + "wasmparser 0.254.2", + "wasmprinter", + "wasmtime-internal-component-util", + "wasmtime-internal-core 48.0.2", ] [[package]] name = "wasmtime-environ" -version = "48.0.1" +version = "48.0.2" +source = "git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0#ef281a08e10646e8b0592d0081752fef23cab585" dependencies = [ "anyhow", - "cpp_demangle", - "cranelift-bforest 0.135.1", - "cranelift-bitset 0.135.1", - "cranelift-entity 0.135.1", - "gimli 0.33.0", + "cranelift-bforest 0.135.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", + "cranelift-bitset 0.135.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", + "cranelift-entity 0.135.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", + "gimli", "hashbrown 0.17.1", "indexmap", "log", - "object 0.39.1", + "object", "postcard", - "rustc-demangle", - "semver", "serde", "serde_derive", "sha2", "smallvec", "target-lexicon", - "wasm-encoder 0.254.0", - "wasmparser 0.254.0", - "wasmprinter 0.254.0", - "wasmtime-internal-component-util 48.0.1", - "wasmtime-internal-core", -] - -[[package]] -name = "wasmtime-internal-asm-macros" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "cfg-if", + "wasmparser 0.254.2", + "wasmtime-internal-core 48.0.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", ] [[package]] name = "wasmtime-internal-cache" -version = "48.0.1" +version = "48.0.2" dependencies = [ "base64", "directories-next", "log", "postcard", - "rustix 1.1.4", + "rustix", "serde", "serde_derive", "sha2", "toml", - "wasmtime-environ 48.0.1", + "wasmtime-environ 48.0.2", "windows-sys 0.61.2", "zstd", ] [[package]] name = "wasmtime-internal-component-macro" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "anyhow", - "proc-macro2", - "quote", - "syn 2.0.119", - "wasmtime-internal-component-util 36.0.12", - "wasmtime-internal-wit-bindgen 36.0.12", - "wit-parser 0.236.1", -] - -[[package]] -name = "wasmtime-internal-component-macro" -version = "48.0.1" +version = "48.0.2" dependencies = [ "anyhow", "proc-macro2", "quote", "syn 2.0.119", - "wasmtime-internal-component-util 48.0.1", - "wasmtime-internal-wit-bindgen 48.0.1", - "wit-parser 0.254.0", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser 0.254.2", ] [[package]] name = "wasmtime-internal-component-util" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" - -[[package]] -name = "wasmtime-internal-component-util" -version = "48.0.1" +version = "48.0.2" [[package]] name = "wasmtime-internal-core" -version = "48.0.1" +version = "48.0.2" dependencies = [ "anyhow", "hashbrown 0.17.1", @@ -4579,272 +4111,129 @@ dependencies = [ ] [[package]] -name = "wasmtime-internal-cranelift" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +name = "wasmtime-internal-core" +version = "48.0.2" +source = "git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0#ef281a08e10646e8b0592d0081752fef23cab585" dependencies = [ - "anyhow", - "cfg-if", - "cranelift-codegen 0.123.12", - "cranelift-control 0.123.12", - "cranelift-entity 0.123.12", - "cranelift-frontend 0.123.12", - "cranelift-native 0.123.12", - "gimli 0.32.3", - "itertools 0.14.0", - "log", - "object 0.37.3", - "pulley-interpreter 36.0.12", - "smallvec", - "target-lexicon", - "thiserror 2.0.19", - "wasmparser 0.236.1", - "wasmtime-environ 36.0.12", - "wasmtime-internal-math", - "wasmtime-internal-versioned-export-macros 36.0.12", + "hashbrown 0.17.1", + "libm", + "serde", ] [[package]] name = "wasmtime-internal-cranelift" -version = "48.0.1" -dependencies = [ - "cranelift-codegen 0.135.1", - "cranelift-control 0.135.1", - "cranelift-entity 0.135.1", - "cranelift-frontend 0.135.1", - "cranelift-native 0.135.1", - "gimli 0.33.0", +version = "48.0.2" +dependencies = [ + "cranelift-codegen", + "cranelift-control", + "cranelift-entity 0.135.2", + "cranelift-frontend", + "cranelift-native", + "gimli", "itertools 0.14.0", "log", - "object 0.39.1", - "pulley-interpreter 48.0.1", + "object", + "pulley-interpreter", "smallvec", "target-lexicon", - "thiserror 2.0.19", - "wasmparser 0.254.0", - "wasmtime-environ 48.0.1", - "wasmtime-internal-core", - "wasmtime-internal-unwinder 48.0.1", - "wasmtime-internal-versioned-export-macros 48.0.1", -] - -[[package]] -name = "wasmtime-internal-fiber" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "anyhow", - "cc", - "cfg-if", - "libc", - "rustix 1.1.4", - "wasmtime-internal-asm-macros", - "wasmtime-internal-versioned-export-macros 36.0.12", - "windows-sys 0.60.2", + "thiserror 2.0.21", + "wasmparser 0.254.2", + "wasmtime-environ 48.0.2", + "wasmtime-internal-core 48.0.2", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", ] [[package]] name = "wasmtime-internal-fiber" -version = "48.0.1" +version = "48.0.2" dependencies = [ "cc", "libc", - "rustix 1.1.4", - "wasmtime-environ 48.0.1", - "wasmtime-internal-versioned-export-macros 48.0.1", + "rustix", + "wasmtime-environ 48.0.2", + "wasmtime-internal-versioned-export-macros", "windows-sys 0.61.2", ] [[package]] name = "wasmtime-internal-jit-debug" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "cc", - "wasmtime-internal-versioned-export-macros 36.0.12", -] - -[[package]] -name = "wasmtime-internal-jit-debug" -version = "48.0.1" +version = "48.0.2" dependencies = [ "cc", - "object 0.39.1", - "rustix 1.1.4", - "wasmtime-internal-versioned-export-macros 48.0.1", -] - -[[package]] -name = "wasmtime-internal-jit-icache-coherence" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "anyhow", - "cfg-if", - "libc", - "windows-sys 0.60.2", + "object", + "rustix", + "wasmtime-internal-versioned-export-macros", ] [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "48.0.1" +version = "48.0.2" dependencies = [ "libc", - "wasmtime-internal-core", + "wasmtime-internal-core 48.0.2", "windows-sys 0.61.2", ] -[[package]] -name = "wasmtime-internal-math" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "libm", -] - -[[package]] -name = "wasmtime-internal-slab" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" - -[[package]] -name = "wasmtime-internal-unwinder" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "anyhow", - "cfg-if", - "log", - "object 0.37.3", -] - [[package]] name = "wasmtime-internal-unwinder" -version = "48.0.1" +version = "48.0.2" dependencies = [ - "cranelift-codegen 0.135.1", + "cranelift-codegen", "log", - "object 0.39.1", - "wasmtime-environ 48.0.1", -] - -[[package]] -name = "wasmtime-internal-versioned-export-macros" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "object", + "wasmtime-environ 48.0.2", ] [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "48.0.1" +version = "48.0.2" dependencies = [ "proc-macro2", "quote", "syn 2.0.119", ] -[[package]] -name = "wasmtime-internal-winch" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "anyhow", - "cranelift-codegen 0.123.12", - "gimli 0.32.3", - "object 0.37.3", - "target-lexicon", - "wasmparser 0.236.1", - "wasmtime-environ 36.0.12", - "wasmtime-internal-cranelift 36.0.12", - "winch-codegen", -] - -[[package]] -name = "wasmtime-internal-wit-bindgen" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "anyhow", - "bitflags 2.13.1", - "heck", - "indexmap", - "wit-parser 0.236.1", -] - [[package]] name = "wasmtime-internal-wit-bindgen" -version = "48.0.1" +version = "48.0.2" dependencies = [ "anyhow", - "bitflags 2.13.1", + "bitflags 2.13.2", "heck", "indexmap", - "wit-component 0.254.0", - "wit-parser 0.254.0", + "wit-component 0.254.2", + "wit-parser 0.254.2", ] [[package]] name = "wasmtime-wasi" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "48.0.2" dependencies = [ - "anyhow", "async-trait", - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", - "cap-fs-ext", - "cap-net-ext", - "cap-rand", - "cap-std", - "cap-time-ext", - "fs-set-times", - "futures", - "io-extras 0.18.4", - "io-lifetimes 2.0.4", - "rustix 1.1.4", - "system-interface", - "thiserror 2.0.19", - "tokio", - "tracing", - "url", - "wasmtime 36.0.12", - "wasmtime-wasi-io 36.0.12", - "windows-sys 0.60.2", -] - -[[package]] -name = "wasmtime-wasi" -version = "48.0.1" -dependencies = [ - "async-trait", - "bitflags 2.13.1", - "bytes", - "cap-primitives 4.0.3", + "cap-primitives", "futures", - "rand 0.10.2", - "rustix 1.1.4", + "rand 0.10.3", + "rustix", "rustix-linux-procfs", - "thiserror 2.0.19", + "thiserror 2.0.21", "tokio", "tracing", "url", - "wasmtime 48.0.1", - "wasmtime-wasi-io 48.0.1", - "wiggle 48.0.1", + "wasmtime", + "wasmtime-wasi-io 48.0.2", + "wiggle", "windows-sys 0.61.2", "winx", ] [[package]] name = "wasmtime-wasi-http" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "48.0.2" +source = "git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0#ef281a08e10646e8b0592d0081752fef23cab585" dependencies = [ - "anyhow", "async-trait", "bytes", "futures", @@ -4852,52 +4241,53 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "pin-project-lite", "rustls", "tokio", "tokio-rustls", "tracing", - "wasmtime 36.0.12", - "wasmtime-wasi 36.0.12", - "wasmtime-wasi-io 36.0.12", - "webpki-roots 0.26.11", + "wasmtime", + "wasmtime-wasi", + "wasmtime-wasi-io 48.0.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0)", + "webpki-roots", ] [[package]] name = "wasmtime-wasi-io" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "48.0.2" dependencies = [ - "anyhow", "async-trait", "bytes", "futures", - "wasmtime 36.0.12", + "tracing", + "wasmtime", ] [[package]] name = "wasmtime-wasi-io" -version = "48.0.1" +version = "48.0.2" +source = "git+https://github.com/G-Core/wasmtime.git?branch=release-48.0.0#ef281a08e10646e8b0592d0081752fef23cab585" dependencies = [ "async-trait", "bytes", "futures", "tracing", - "wasmtime 48.0.1", + "wasmtime", ] [[package]] name = "wasmtime-wasi-nn" -version = "48.0.1" +version = "48.0.2" dependencies = [ "candle-core", "candle-nn", "candle-transformers", "openvino", - "thiserror 2.0.19", + "thiserror 2.0.21", "tracing", "walkdir", - "wasmtime 48.0.1", - "wiggle 48.0.1", + "wasmtime", + "wiggle", "windows", ] @@ -4912,33 +4302,24 @@ dependencies = [ [[package]] name = "wast" -version = "254.0.0" +version = "261.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ed4dfc8f6b9fc38b231065e2cdfbf7359af5ab945990abf09658dcc63c3e32" +checksum = "776443145731a4062e5b0d392892a2005909b6ab72d9fdc3cad53dd1a714e44a" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.254.0", + "wasm-encoder 0.261.0", ] [[package]] name = "wat" -version = "1.254.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7127f7f9b8f127c879991cecd35f494e4628bae1b0874c681414d8d8831e952c" -dependencies = [ - "wast 254.0.0", -] - -[[package]] -name = "webpki-roots" -version = "0.26.11" +version = "1.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +checksum = "a7b4d1a49ea73a8f3326e74e3a05db667001b16bd1035ed3356fc1a0ed05ca7f" dependencies = [ - "webpki-roots 1.0.9", + "wast 261.0.0", ] [[package]] @@ -4952,74 +4333,36 @@ dependencies = [ [[package]] name = "wiggle" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "48.0.2" dependencies = [ - "anyhow", - "async-trait", - "bitflags 2.13.1", - "thiserror 2.0.19", - "tracing", - "wasmtime 36.0.12", - "wiggle-macro 36.0.12", -] - -[[package]] -name = "wiggle" -version = "48.0.1" -dependencies = [ - "bitflags 2.13.1", - "thiserror 2.0.19", + "bitflags 2.13.2", + "thiserror 2.0.21", "tracing", - "wasmtime 48.0.1", - "wasmtime-environ 48.0.1", - "wiggle-macro 48.0.1", -] - -[[package]] -name = "wiggle-generate" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "anyhow", - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", - "witx", + "wasmtime", + "wasmtime-environ 48.0.2", + "wiggle-macro", ] [[package]] name = "wiggle-generate" -version = "48.0.1" +version = "48.0.2" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.119", - "wasmtime-environ 48.0.1", + "wasmtime-environ 48.0.2", "witx", ] [[package]] name = "wiggle-macro" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" +version = "48.0.2" dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "wiggle-generate 36.0.12", -] - -[[package]] -name = "wiggle-macro" -version = "48.0.1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "wiggle-generate 48.0.1", + "wiggle-generate", ] [[package]] @@ -5044,7 +4387,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5053,25 +4396,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "winch-codegen" -version = "36.0.12" -source = "git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0#62166f65ac52a1f164419097a2a533ff50e4d07b" -dependencies = [ - "anyhow", - "cranelift-assembler-x64 0.123.12", - "cranelift-codegen 0.123.12", - "gimli 0.32.3", - "regalloc2 0.12.2", - "smallvec", - "target-lexicon", - "thiserror 2.0.19", - "wasmparser 0.236.1", - "wasmtime-environ 36.0.12", - "wasmtime-internal-cranelift 36.0.12", - "wasmtime-internal-math", -] - [[package]] name = "windows" version = "0.52.0" @@ -5183,6 +4507,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -5351,8 +4684,8 @@ version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" dependencies = [ - "bitflags 2.13.1", - "windows-sys 0.52.0", + "bitflags 2.13.2", + "windows-sys 0.59.0", ] [[package]] @@ -5368,7 +4701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1936c26cb24b93dc36bf78fb5dc35c55cd37f66ecdc2d2663a717d9fb3ee951e" dependencies = [ "anyhow", - "bitflags 2.13.1", + "bitflags 2.13.2", "indexmap", "log", "serde", @@ -5382,39 +4715,21 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.254.0" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0e65bb94c369b3c4741ce3d1d2704b1fec93db7c540df0e521a097e7ceeb5be" +checksum = "1c96b3cea6838dbea55173baa56991fcb087896c321b0a770db201e5a0bd3506" dependencies = [ "anyhow", - "bitflags 2.13.1", + "bitflags 2.13.2", "indexmap", "log", "serde", "serde_derive", "serde_json", - "wasm-encoder 0.254.0", - "wasm-metadata 0.254.0", - "wasmparser 0.254.0", - "wit-parser 0.254.0", -] - -[[package]] -name = "wit-parser" -version = "0.236.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e4833a20cd6e85d6abfea0e63a399472d6f88c6262957c17f546879a80ba15" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.236.1", + "wasm-encoder 0.254.2", + "wasm-metadata 0.254.2", + "wasmparser 0.254.2", + "wit-parser 0.254.2", ] [[package]] @@ -5438,9 +4753,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.254.0" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1655131e4f7d3f0cb141f6eca71315ca40eff0f3d4de7cff0a82bacedd8c89b4" +checksum = "22672d61bfb9df46a16b46ce38522293aee1cc4749fc84a3464ce4083b3d5fd6" dependencies = [ "anyhow", "hashbrown 0.17.1", @@ -5452,7 +4767,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-ident", - "wasmparser 0.254.0", + "wasmparser 0.254.2", ] [[package]] @@ -5469,15 +4784,15 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "xxhash-rust" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" +checksum = "550a2b930b62486a393c52d5c3b84bff264b28aa437ed64694d31e93b1757af7" [[package]] name = "yoke" @@ -5498,7 +4813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", - "yoke-derive 0.8.2", + "yoke-derive 0.8.4", "zerofrom", ] @@ -5511,35 +4826,35 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "synstructure", + "synstructure 0.13.2", ] [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", - "synstructure", + "syn 3.0.6", + "synstructure 0.14.0", ] [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" dependencies = [ "proc-macro2", "quote", @@ -5557,27 +4872,27 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", - "synstructure", + "syn 3.0.6", + "synstructure 0.14.0", ] [[package]] name = "zeroize" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke 0.8.3", @@ -5586,9 +4901,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke 0.8.3", "zerofrom", @@ -5597,13 +4912,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -5638,18 +4953,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.4" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +version = "2.1.0+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 5074625..d0ab059 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,13 +15,12 @@ tracing = "0.1" hyper = { version = "1", features = ["full"] } http = "1.4" async-trait = "0.1" -wasmtime = { git = "https://github.com/G-Core/wasmtime.git", features = ["component-model"], branch = "release-36.0.0"} -wasmtime-wasi = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-36.0.0"} -wasmtime-wasi-io = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-36.0.0"} -wasi-common = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-36.0.0"} -wasmtime-wasi-nn = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-36.0.0", features = ["openvino", "candle"] } -wasmtime-wasi-http = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-36.0.0"} -wasmtime-environ = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-36.0.0"} +wasmtime = { git = "https://github.com/G-Core/wasmtime.git", features = ["component-model"], branch = "release-48.0.0" } +wasmtime-wasi = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-48.0.0" } +wasmtime-wasi-io = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-48.0.0" } +wasmtime-wasi-nn = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-48.0.0", features = ["openvino", "candle"] } +wasmtime-wasi-http = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-48.0.0" } +wasmtime-environ = { git = "https://github.com/G-Core/wasmtime.git", branch = "release-48.0.0" } bytesize = "2.3.1" clap = { version = "4", features = ["derive"] } @@ -56,7 +55,7 @@ http = { workspace = true } tokio = { workspace = true } wasmtime = { workspace = true } smol_str = { workspace = true } -async-trait = {workspace = true} +async-trait = { workspace = true } clap = { version = "4.6", features = ["derive"] } pretty_env_logger = "0.5" runtime = { path = "crates/runtime", default-features = false } @@ -69,7 +68,7 @@ cache = { path = "crates/cache" } hyper-tls = "0.6" hyper-util = { version = "0.1", features = ["client", "client-legacy", "http1", "tokio"] } http-body-util = "0.1" -bytesize = { workspace = true} +bytesize = { workspace = true } [target.'cfg(target_family = "unix")'.dependencies] shellflip = "2.1.2" diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs index b0d4180..7913482 100644 --- a/crates/cache/src/lib.rs +++ b/crates/cache/src/lib.rs @@ -49,42 +49,84 @@ impl CacheImpl { pub fn new(backend: Arc) -> Self { Self { backend } } + + /// Get the value associated with `key`. + pub async fn get(&self, key: &str) -> Result, Error> { + self.backend.get(key).await + } + + /// Set the value for `key` with an optional expiry in milliseconds. + pub async fn set(&self, key: &str, value: Payload, ttl_ms: Option) -> Result<(), Error> { + self.backend.set(key, value, ttl_ms).await + } + + /// Delete the key-value pair associated with `key`. + pub async fn delete(&self, key: &str) -> Result<(), Error> { + self.backend.delete(key).await + } + + /// Check whether `key` exists in the cache. + pub async fn exists(&self, key: &str) -> Result { + self.backend.exists(key).await + } + + /// Increment the integer value stored at `key` by `delta`. + pub async fn incr(&self, key: &str, delta: i64) -> Result { + self.backend.incr(key, delta).await + } + + /// Set or update the expiry of `key` to `ttl_ms` milliseconds from now. + pub async fn expire(&self, key: &str, ttl_ms: u64) -> Result { + self.backend.expire(key, ttl_ms).await + } + + /// Purge every cache entry owned by the calling application. + pub async fn purge(&self) -> Result { + self.backend.purge().await + } + + /// Purge every cache entry whose key begins with `prefix`. + pub async fn purge_prefix(&self, prefix: &str) -> Result { + self.backend.purge_prefix(prefix).await + } } // Empty marker trait impl cache_types::Host for CacheImpl {} // Implement the sync-style cache interface (gcore:fastedge/cache-sync) -// This is simpler and serializes calls through &mut self. +// This is simpler and serializes calls through &mut self. Every method +// delegates to the inherent `CacheImpl` method of the same name, which the +// ProxyWasm host functions also call. impl cache_sync::Host for CacheImpl { async fn get(&mut self, key: String) -> Result, Error> { - self.backend.get(&key).await + CacheImpl::get(self, &key).await } async fn set(&mut self, key: String, value: Payload, ttl_ms: Option) -> Result<(), Error> { - self.backend.set(&key, value, ttl_ms).await + CacheImpl::set(self, &key, value, ttl_ms).await } async fn delete(&mut self, key: String) -> Result<(), Error> { - self.backend.delete(&key).await + CacheImpl::delete(self, &key).await } async fn exists(&mut self, key: String) -> Result { - self.backend.exists(&key).await + CacheImpl::exists(self, &key).await } async fn incr(&mut self, key: String, delta: i64) -> Result { - self.backend.incr(&key, delta).await + CacheImpl::incr(self, &key, delta).await } async fn expire(&mut self, key: String, ttl_ms: u64) -> Result { - self.backend.expire(&key, ttl_ms).await + CacheImpl::expire(self, &key, ttl_ms).await } async fn purge(&mut self) -> Result { - self.backend.purge().await + CacheImpl::purge(self).await } async fn purge_prefix(&mut self, prefix: String) -> Result { - self.backend.purge_prefix(&prefix).await + CacheImpl::purge_prefix(self, &prefix).await } } diff --git a/crates/http-backend/src/access_log.rs b/crates/http-backend/src/access_log.rs new file mode 100644 index 0000000..cdc88eb --- /dev/null +++ b/crates/http-backend/src/access_log.rs @@ -0,0 +1,58 @@ +//! Outbound-request access-log seam. +//! +//! Distinct from [`crate::stats`] (billing/Prometheus timing): this sink emits +//! one access-log record per external HTTP request the guest makes. It is kept +//! dependency-free — primitive argument types only — so `http-backend` need not +//! depend on the runtime's access-log types. The concrete implementation (which +//! builds the runtime record and ships it) lives in the embedder. + +use smol_str::SmolStr; +use std::sync::Arc; +use std::time::Duration; + +/// Sink for one completed outbound (external) HTTP request. +/// +/// `status` is `0` when the request failed before a response was received +/// (connection error / timeout). Implementations must be non-blocking — this is +/// called on the request hot path. +pub trait ExtRequestLog: Send + Sync { + /// Record one completed outbound request, correlated to the inbound request + /// via `traceparent`. + fn log_ext_request( + &self, + traceparent: &str, + app_id: u64, + method: &str, + uri: &str, + status: u16, + elapsed: Duration, + ); +} + +/// A cloneable, connector-agnostic handle carrying everything needed to log one +/// outbound request once it completes. Used by the WASI-HTTP path, where the +/// send happens in a spawned task and the [`crate::Backend`] (generic over its +/// connector) is not available; the handle is captured before the spawn. +#[derive(Clone)] +pub struct ExtRequestLogHandle { + log: Arc, + traceparent: SmolStr, + app_id: u64, +} + +impl ExtRequestLogHandle { + pub fn new(log: Arc, traceparent: SmolStr, app_id: u64) -> Self { + Self { + log, + traceparent, + app_id, + } + } + + /// Emit one record for a completed outbound request. `status` is `0` on + /// failure (no response received). + pub fn log(&self, method: &str, uri: &str, status: u16, elapsed: Duration) { + self.log + .log_ext_request(&self.traceparent, self.app_id, method, uri, status, elapsed); + } +} diff --git a/crates/http-backend/src/lib.rs b/crates/http-backend/src/lib.rs index e558387..02aa886 100644 --- a/crates/http-backend/src/lib.rs +++ b/crates/http-backend/src/lib.rs @@ -1,5 +1,7 @@ +pub mod access_log; pub mod stats; +use crate::access_log::{ExtRequestLog, ExtRequestLogHandle}; use smol_str::{SmolStr, ToSmolStr}; use std::fmt::Debug; use std::future::Future; @@ -92,6 +94,12 @@ pub struct Backend { epoch_exclude_http_wait: bool, cdn_real_host: Option, app_id: Option, + /// Sink for the per-outbound-request access log. Baked into the backend + /// template by the embedder; `None` disables outbound-request logging. + ext_http_log: Option>, + /// W3C `traceparent` of the inbound request, set per request so each + /// outbound record can be correlated back to it. + traceparent: Option, } pub struct Builder { @@ -144,6 +152,8 @@ impl Builder { epoch_exclude_http_wait: false, cdn_real_host: None, app_id: None, + ext_http_log: None, + traceparent: None, } } } @@ -183,6 +193,48 @@ impl Backend { self.app_id } + /// Set the outbound-request access-log sink (baked into the template once). + pub fn set_ext_http_log(&mut self, log: Arc) { + self.ext_http_log = Some(log); + } + + /// Set the inbound `traceparent` used to correlate outbound records. + pub fn set_traceparent(&mut self, traceparent: SmolStr) { + self.traceparent = Some(traceparent); + } + + /// Snapshot the outbound-request log sink + correlation context into a + /// cloneable handle, for logging from a context where `self` is not + /// available (e.g. the WASI-HTTP spawned send task). `None` when no sink or + /// `traceparent` is set. + pub fn ext_request_log_handle(&self) -> Option { + match (self.ext_http_log.as_ref(), self.traceparent.as_ref()) { + (Some(log), Some(traceparent)) => Some(ExtRequestLogHandle::new( + log.clone(), + traceparent.clone(), + self.app_id.unwrap_or(0), + )), + _ => None, + } + } + + /// Emit one access-log record for a completed outbound request. No-op when + /// no sink or no `traceparent` is set. `status` is `0` on failure. + pub fn log_ext_request( + &self, + app_id: u64, + method: &str, + uri: &str, + status: u16, + elapsed: Duration, + ) { + if let (Some(log), Some(traceparent)) = + (self.ext_http_log.as_ref(), self.traceparent.as_ref()) + { + log.log_ext_request(traceparent, app_id, method, uri, status, elapsed); + } + } + /// Set external request stats pub fn set_ext_http_stats(&mut self, stats: Arc) { self.ext_http_stats.replace(stats); @@ -424,6 +476,11 @@ where self.max_sub_requests -= 1; } + // Capture the outbound method/target for the access-log record before + // `make_request` consumes the request. + let log_method = method_str(&req.method); + let log_uri = req.uri.to_smolstr(); + let request = self.make_request(req).map_err(|error| { warn!(cause=?error, "making request to backend"); HttpError::RequestError @@ -483,14 +540,43 @@ where }) } .await; + let elapsed = started.elapsed(); if self.epoch_exclude_http_wait { self.epoch_pause_ms - .fetch_add(started.elapsed().as_millis() as u64, Ordering::Relaxed); + .fetch_add(elapsed.as_millis() as u64, Ordering::Relaxed); } + + // Emit one access-log record for this outbound request. Failures before a + // response (connection error / timeout) are logged with status 0. + let status = match &result { + Ok(response) => response.status, + Err(_) => 0, + }; + self.log_ext_request( + self.app_id.unwrap_or(0), + &log_method, + &log_uri, + status, + elapsed, + ); result } } +/// Stable string label for a guest [`Method`], for the access log. +fn method_str(method: &Method) -> SmolStr { + match method { + Method::Get => "GET", + Method::Post => "POST", + Method::Put => "PUT", + Method::Delete => "DELETE", + Method::Head => "HEAD", + Method::Patch => "PATCH", + Method::Options => "OPTIONS", + } + .into() +} + // extract canonical host name fn canonical_host_name(headers: &Headers, original_uri: &Uri) -> Result { let host = headers.iter().find_map(|(k, v)| { diff --git a/crates/http-service/Cargo.toml b/crates/http-service/Cargo.toml index 8abc003..2456627 100644 --- a/crates/http-service/Cargo.toml +++ b/crates/http-service/Cargo.toml @@ -7,7 +7,6 @@ authors.workspace = true [features] default = [] -metrics = ["runtime/metrics"] [dependencies] anyhow = { workspace = true } @@ -17,7 +16,6 @@ http = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } wasmtime-wasi-nn = { workspace = true } -wasi-common = { workspace = true } wasmtime-wasi-http = { workspace = true } tracing = { workspace = true } smol_str = { workspace = true } @@ -27,7 +25,6 @@ http-backend = { path = "../http-backend" } utils = { path = "../utils" } secret = { path = "../secret" } cache = { path = "../cache" } -nanoid = "0.5" async-trait = "0.1" hyper-util = { version = "0.1", features = ["server", "server-graceful"] } http-body-util = "0.1" diff --git a/crates/http-service/src/executor/http.rs b/crates/http-service/src/executor/http.rs index d74b675..9f94ab2 100644 --- a/crates/http-service/src/executor/http.rs +++ b/crates/http-service/src/executor/http.rs @@ -13,7 +13,7 @@ use runtime::{InstancePre, store::StoreBuilder}; use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::time::Duration; -use wasmtime_wasi_http::body::HyperOutgoingBody; +use wasmtime_wasi_http::p2::body::HyperOutgoingBody; /// Execute context used by ['HttpService'] #[derive(Clone)] @@ -98,6 +98,13 @@ where let mut http_backend = self.backend; http_backend.set_app_id(self.app_id); + if let Some(traceparent) = parts + .headers + .get(executor::TRACEPARENT) + .and_then(|v| v.to_str().ok()) + { + http_backend.set_traceparent(traceparent.into()); + } http_backend .propagate_headers(parts.headers.clone()) .context("propagate headers")?; @@ -133,9 +140,9 @@ where // declared minimum memory exceeds `mem_limit`) is recorded by the // limiter; classify it as out-of-memory instead of a generic error. if store.is_oom() { - return Err(runtime::store::OutOfMemory(error).into()); + return Err(runtime::store::OutOfMemory(error.into()).into()); } - return Err(error); + return Err(error.into()); } }; let http_handler = @@ -159,7 +166,7 @@ where .write_msg(format!("Execution error: {}", error)) .await; } - return Err(error); + return Err(error.into()); } }; let status_code = StatusCode::try_from(resp.status)?; @@ -178,7 +185,7 @@ where let body = resp .body - .map(|b| Full::from(b).map_err(|never| match never {}).boxed()) + .map(|b| Full::from(b).map_err(|never| match never {}).boxed_unsync()) .unwrap_or_default(); builder.body(body).map_err(anyhow::Error::msg) } @@ -374,11 +381,11 @@ mod tests { DUMMY_SAMPLE }; let wasm_sample = componentize_if_necessary(bytes)?; - Component::new(&self.engine, wasm_sample) + Ok(Component::new(&self.engine, wasm_sample)?) } fn load_module(&self, _id: u64) -> anyhow::Result { - Module::new(&self.engine, DUMMY_SAMPLE) + Ok(Module::new(&self.engine, DUMMY_SAMPLE)?) } } @@ -495,7 +502,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -527,7 +534,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -586,7 +593,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -641,7 +648,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -669,7 +676,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -697,7 +704,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -725,7 +732,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -757,7 +764,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -791,7 +798,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -825,7 +832,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -855,7 +862,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -886,7 +893,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -918,7 +925,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); @@ -944,7 +951,7 @@ mod tests { Request::builder().method("GET").uri("/").body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); diff --git a/crates/http-service/src/executor/mod.rs b/crates/http-service/src/executor/mod.rs index 1c91073..3515575 100644 --- a/crates/http-service/src/executor/mod.rs +++ b/crates/http-service/src/executor/mod.rs @@ -12,7 +12,7 @@ use http_body_util::BodyExt; use hyper::body::Body; use runtime::{App, WasmEngine}; use smol_str::SmolStr; -use wasmtime_wasi_http::body::HyperOutgoingBody; +use wasmtime_wasi_http::p2::body::HyperOutgoingBody; pub use http::HttpExecutorImpl; pub use wasi_http::WasiHttpExecutorImpl; diff --git a/crates/http-service/src/executor/wasi_http.rs b/crates/http-service/src/executor/wasi_http.rs index 90a083c..5b16546 100644 --- a/crates/http-service/src/executor/wasi_http.rs +++ b/crates/http-service/src/executor/wasi_http.rs @@ -6,7 +6,7 @@ use crate::executor; use crate::executor::{HttpExecutor, X_CDN_REAL_HOST}; use crate::state::HttpState; use ::http::{HeaderMap, Request, Response, Uri, header}; -use anyhow::{Context, anyhow, bail}; +use anyhow::{Context, anyhow}; use async_trait::async_trait; use http_backend::Backend; use http_body_util::{BodyExt, Full}; @@ -15,9 +15,10 @@ use runtime::util::stats::{StatsTimer, StatsVisitor}; use runtime::{InstancePre, store::StoreBuilder}; use smol_str::SmolStr; use tracing::Instrument; -use wasmtime_wasi_http::bindings::ProxyPre; -use wasmtime_wasi_http::bindings::http::types::Scheme; -use wasmtime_wasi_http::{WasiHttpView, body::HyperOutgoingBody}; +use wasmtime_wasi_http::WasiHttpView; +use wasmtime_wasi_http::p2::bindings::ProxyPre; +use wasmtime_wasi_http::p2::bindings::http::types::Scheme; +use wasmtime_wasi_http::p2::body::HyperOutgoingBody; /// Execute context used by ['HttpService'] #[derive(Clone)] @@ -77,7 +78,7 @@ where .map_err(|_| anyhow!("body read error"))? .to_bytes(); let body = Full::new(body).map_err(|never| match never {}); - let body = body.boxed(); + let body: HyperOutgoingBody = body.boxed_unsync(); let properties = executor::get_properties(&parts.headers); let mut store_builder = self @@ -94,6 +95,13 @@ where } let mut http_backend = self.backend; http_backend.set_app_id(self.app_id); + if let Some(traceparent) = parts + .headers + .get(executor::TRACEPARENT) + .and_then(|v| v.to_str().ok()) + { + http_backend.set_traceparent(traceparent.into()); + } http_backend.epoch_exclude_http_wait(self.epoch_exclude_http_wait); if let Some(counter) = epoch_pause_ms { http_backend.set_epoch_pause_ms(counter); @@ -140,11 +148,15 @@ where let request = Request::from_parts(parts, body); let req = store .data_mut() + .http() .new_incoming_request(Scheme::Http, request) + .map_err(anyhow::Error::from) .context("new incoming request")?; let out = store .data_mut() + .http() .new_response_outparam(sender) + .map_err(anyhow::Error::from) .context("new response outparam")?; let proxy_pre = ProxyPre::new(instance_pre)?; @@ -155,9 +167,9 @@ where // declared minimum memory exceeds `mem_limit`) is recorded by the // limiter; classify it as out-of-memory instead of a generic error. if store.is_oom() { - return Err(runtime::store::OutOfMemory(error).into()); + return Err(runtime::store::OutOfMemory(error.into()).into()); } - return Err(error); + return Err(error.into()); } }; @@ -173,12 +185,15 @@ where ) .await { - Ok(inner) => inner, + // Convert into `anyhow::Error` here so the task's error type + // stays what `fail_reason_of`/`map_err` classify. The + // conversion rebuilds the context chain, so `root_cause()` + // still downcasts to `wasmtime::Trap`. + Ok(inner) => inner.map_err(anyhow::Error::from), // tokio timeout elapsed (outer deadline hit). Err(elapsed) => Err(elapsed.into()), }; if let Err(e) = exec_result { - tracing::warn!(cause=?e, "incoming handler"); // Record the failure reason on the shared stats. The response // headers may already have been flushed (the guest called // `response-outparam::set` before trapping mid-body), in which @@ -204,18 +219,49 @@ where match receiver.await { Ok(Ok(response)) => { + // The response headers are already on their way to the client, so a + // later failure of the task (e.g. an epoch interrupt while streaming + // the body) can no longer be propagated to the caller. This watcher + // is the only place it can be reported. + // + // It is spawned *only* on this path. On every other path the error + // still reaches the caller and is logged upstream, so watching here + // as well would log the same failure twice. + tokio::task::spawn( + async move { + match task.await { + Ok(Ok(())) => {} + Ok(Err(e)) => tracing::warn!(cause=?e, "incoming handler"), + // Panic or cancellation. Nothing else observes the task + // once headers are flushed, so report it here too. + Err(e) => tracing::warn!(cause=?e, "incoming handler task"), + } + } + .in_current_span(), + ); stats.status_code(response.status().as_u16()); Ok(response) } + // The guest set an error response, which is returned to the caller + // below - so the task must not log it as well. Dropping the handle only + // detaches the task: it still runs to completion and still records + // `fail_reason` on the shared stats. Ok(Err(error)) => Err(error.into()), Err(_) => { let e = match task.await { - Ok(r) => { - r.expect_err("if the receiver has an error, the task must have failed") - } + // The guest returned without ever setting the outparam, so there + // is no task error to attribute. Synthesise one rather than + // asserting the task failed, which would panic on this path. + Ok(Ok(())) => anyhow!("handler completed without setting a response"), + Ok(Err(e)) => e, Err(e) => e.into(), }; - bail!("guest never invoked `response-outparam::set` method: {e:?}") + // Attach context instead of formatting `e` into a new message so the + // source chain is preserved: `map_err`/`fail_reason_of` rely on + // `root_cause()` downcasting to `wasmtime::Trap` / `Elapsed` to + // classify timeouts. Flattening it into a string made every failure + // here look like a generic execute error (530 instead of 532). + Err(e).context("guest never invoked `response-outparam::set` method") } } } diff --git a/crates/http-service/src/lib.rs b/crates/http-service/src/lib.rs index e9e46f4..e6093c4 100644 --- a/crates/http-service/src/lib.rs +++ b/crates/http-service/src/lib.rs @@ -15,17 +15,19 @@ use http_backend::SERVER_NAME_HEADER; use http_body_util::{BodyExt, Empty, Full}; use hyper::{body::Body, server::conn::http1, service::service_fn}; use hyper_util::{client::legacy::connect::Connect, rt::TokioIo}; -#[cfg(feature = "metrics")] use runtime::util::metrics; use runtime::util::stats::StatsVisitor; use runtime::{ - App, AppResult, ContextT, Router, WasmEngine, WasmEngineBuilder, app::Status, service::Service, + App, AppResult, ContextT, Router, WasmEngine, WasmEngineBuilder, + access_log::{AccessLog, RequestKind}, + app::Status, + service::Service, }; use smol_str::{SmolStr, ToSmolStr}; use state::HttpState; use tokio::{net::TcpListener, time::error::Elapsed}; use tracing::Instrument; -pub use wasmtime_wasi_http::body::HyperOutgoingBody; +pub use wasmtime_wasi_http::p2::body::HyperOutgoingBody; pub mod executor; pub mod state; @@ -38,7 +40,6 @@ type OwnedFd = std::os::fd::OwnedFd; #[cfg(not(target_family = "unix"))] type OwnedFd = std::os::raw::c_int; -#[cfg(feature = "metrics")] const HTTP_LABEL: &[&str; 1] = &["http"]; const FASTEDGE_INTERNAL_ERROR: u16 = 530; @@ -220,7 +221,7 @@ where // Allow re-importing of `wasi:clocks/wall-clock@0.2.0` wasmtime_wasi::p2::add_to_linker_async(linker)?; linker.allow_shadowing(true); - wasmtime_wasi_http::add_to_linker_async(linker)?; + wasmtime_wasi_http::p2::add_to_linker_async(linker)?; wasmtime_wasi_nn::wit::add_to_linker(linker, |data: &mut runtime::Data<_>| { WasiNnView::new(&mut data.table, &mut data.wasi_nn) })?; @@ -260,24 +261,70 @@ where T::Executor: HttpExecutor + Send + Sync, S: StatsVisitor + Send + 'static, { - /// handle HTTP request. + /// handle HTTP request, emitting exactly one system request-log record for + /// every processing outcome (success, failure, or early rejection). async fn handle_request( &self, - mut request: hyper::Request, + request: hyper::Request, ) -> Result> where B: BodyExt + Send, ::Data: Send, { let traceparent = remote_traceparent(&request); + let mut log = AccessLog::new(traceparent.to_string(), RequestKind::Http); + log.method = Some(request.method().as_str().to_string()); + log.uri = Some(request.uri().to_string()); + + let result = self + .handle_request_inner(request, &traceparent, &mut log) + .await; + + match &result { + Ok(response) => { + log.status_code = response.status().as_u16(); + log.internal_status_code = response + .headers() + .get(X_CDN_INTERNAL_STATUS) + .and_then(|v| v.to_str().ok()) + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + } + Err(_) => { + log.status_code = FASTEDGE_INTERNAL_ERROR; + log.internal_status_code = INTERNAL_STATUS_EXECUTE_ERROR; + } + } + self.context.access_log_sender().log(log); + result + } + + async fn handle_request_inner( + &self, + mut request: hyper::Request, + traceparent: &SmolStr, + log: &mut AccessLog, + ) -> Result> + where + B: BodyExt + Send, + ::Data: Send, + { request .headers_mut() .extend(app_req_headers(self.context.append_headers())); + // Pin the resolved traceparent onto the request (it may have been + // generated when the client sent none) so the executor/backend logs + // outbound requests under the same id the access log uses. + if let Ok(value) = HeaderValue::from_str(traceparent) { + request + .headers_mut() + .insert(HeaderName::from_static(TRACEPARENT), value); + } + // get application name from request URL let app_name = match app_name_from_request(&request) { Err(error) => { - #[cfg(feature = "metrics")] metrics::metrics(AppResult::UNKNOWN, HTTP_LABEL, None, None); tracing::info!(cause=?error, traceparent = %traceparent, "App name not provided"); return not_found(); @@ -307,7 +354,6 @@ where let (app_name, cfg) = match lookup { None => { - #[cfg(feature = "metrics")] metrics::metrics(AppResult::UNKNOWN, HTTP_LABEL, None, None); tracing::info!("Request for unknown application on URL: {}", request.uri()); return not_found(); @@ -315,6 +361,7 @@ where Some((app_name, cfg)) if cfg.status == Status::Draft || cfg.status == Status::Disabled => { + metrics::metrics(AppResult::DISABLED, HTTP_LABEL, None, None); tracing::info!( "Request for disabled application '{}' on URL: {}", app_name, @@ -323,6 +370,7 @@ where return not_found(); } Some((app_name, cfg)) if cfg.status == Status::RateLimited => { + metrics::metrics(AppResult::RATE_LIMITED, HTTP_LABEL, None, None); tracing::info!( "Request for rate limited application '{}' on URL: {}", app_name, @@ -331,6 +379,7 @@ where return too_many_requests(); } Some((app_name, cfg)) if cfg.status == Status::Suspended => { + metrics::metrics(AppResult::SUSPENDED, HTTP_LABEL, None, None); tracing::info!( "Request for suspended application '{}' on URL: {}", app_name, @@ -342,6 +391,10 @@ where Some((app_name, cfg)) => (app_name, cfg), }; + log.app_id = Some(cfg.app_id); + log.client_id = Some(cfg.client_id); + log.app_name = Some(app_name.to_string()); + // get cached execute context for this application let executor = match self .context @@ -350,7 +403,6 @@ where { Ok(executor) => executor, Err(error) => { - #[cfg(feature = "metrics")] metrics::metrics(AppResult::UNKNOWN, HTTP_LABEL, None, None); tracing::warn!(cause=?error, app=%app_name, "failure on getting context" @@ -361,17 +413,23 @@ where let caller_ip = request .headers() - .get(crate::executor::X_REAL_IP) + .get(executor::X_REAL_IP) .and_then(|v| v.to_str().ok()) .and_then(|v| v.parse::().ok()) .unwrap_or(std::net::Ipv4Addr::UNSPECIFIED); let stats = self .context - .new_stats_row(&traceparent, &app_name, caller_ip, &cfg); + .new_stats_row(traceparent, &app_name, caller_ip, &cfg); + if is_stats_excluded_user_agent( + self.context.stats_excluded_user_agent(), + request.headers(), + ) { + tracing::debug!("stats excluded by user agent"); + stats.discard(); + } let response = match executor.execute(request, stats.clone()).await { Ok(mut response) => { - #[cfg(feature = "metrics")] metrics::metrics( AppResult::SUCCESS, &["http"], @@ -389,7 +447,6 @@ where stats.fail_reason(fail_reason as i32); tracing::debug!(?fail_reason, ?traceparent, "stats"); - #[cfg(feature = "metrics")] metrics::metrics( fail_reason, HTTP_LABEL, @@ -408,6 +465,7 @@ where builder.body(msg)? } }; + log.duration = Some(Duration::from_micros(stats.get_time_elapsed())); Ok(response) } .instrument(span) @@ -415,6 +473,17 @@ where } } +/// `true` when `excluded` is set and the request's first `User-Agent` header +/// equals it byte for byte. Used to keep synthetic probes out of the stats store. +fn is_stats_excluded_user_agent(excluded: Option<&str>, headers: &HeaderMap) -> bool { + match excluded { + Some(ua) => headers + .get(http::header::USER_AGENT) + .is_some_and(|v| v.as_bytes() == ua.as_bytes()), + None => false, + } +} + /// Classify an execution error into an [`AppResult`] fail reason. /// /// Kept consistent with the fail-reason mapping in [`map_err`]. Used to record @@ -426,7 +495,7 @@ pub(crate) fn fail_reason_of(error: &Error) -> AppResult { let root_cause = error.root_cause(); if error.chain().any(|e| e.is::()) { AppResult::OOM - } else if let Some(exit) = root_cause.downcast_ref::() { + } else if let Some(exit) = root_cause.downcast_ref::() { if exit.0 == 0 { AppResult::SUCCESS } else { @@ -458,15 +527,15 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody, u16) { AppResult::OOM, Full::new(Bytes::from("fastedge: Out of memory")) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), INTERNAL_STATUS_OUT_OF_MEMORY, ) - } else if let Some(exit) = root_cause.downcast_ref::() { + } else if let Some(exit) = root_cause.downcast_ref::() { if exit.0 == 0 { ( StatusCode::OK.as_u16(), AppResult::SUCCESS, - Empty::new().map_err(|never| match never {}).boxed(), + Empty::new().map_err(|never| match never {}).boxed_unsync(), 0, ) } else { @@ -475,7 +544,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody, u16) { AppResult::OTHER, Full::new(Bytes::from("fastedge: App failed")) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), INTERNAL_STATUS_APP_EXIT_ERROR, ) } @@ -486,7 +555,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody, u16) { AppResult::TIMEOUT, Full::new(Bytes::from("fastedge: Execution timeout")) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), INTERNAL_STATUS_TIMEOUT_INTERRUPT, ), wasmtime::Trap::UnreachableCodeReached => ( @@ -494,7 +563,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody, u16) { AppResult::OOM, Full::new(Bytes::from("fastedge: Out of memory")) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), INTERNAL_STATUS_OUT_OF_MEMORY, ), _ => ( @@ -502,7 +571,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody, u16) { AppResult::OTHER, Full::new(Bytes::from("fastedge: App failed")) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), INTERNAL_STATUS_WASM_TRAP_OTHER, ), } @@ -512,7 +581,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody, u16) { AppResult::TIMEOUT, Full::new(Bytes::from("fastedge: Execution timeout")) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), INTERNAL_STATUS_TIMEOUT_ELAPSED, ) } else if root_cause.to_string().ends_with("deadline has elapsed") { @@ -521,7 +590,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody, u16) { AppResult::TIMEOUT, Full::new(Bytes::from("fastedge: Execution timeout")) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), INTERNAL_STATUS_TIMEOUT_DEADLINE, ) } else { @@ -530,7 +599,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody, u16) { AppResult::OTHER, Full::new(Bytes::from("fastedge: Execute error")) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), INTERNAL_STATUS_EXECUTE_ERROR, ) }; @@ -541,8 +610,9 @@ fn remote_traceparent(req: &hyper::Request) -> SmolStr { req.headers() .get(TRACEPARENT) .and_then(|v| v.to_str().ok()) + .filter(|s| !s.is_empty()) .map(|s| s.to_smolstr()) - .unwrap_or(nanoid::nanoid!().to_smolstr()) + .unwrap_or_else(runtime::new_traceparent) } /// Creates an HTTP 530 response with an `X-CDN-Internal-Status` header. @@ -556,7 +626,7 @@ fn internal_fastedge_error( .body( Full::new(Bytes::from(format!("fastedge: {}", msg))) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), )?) } @@ -567,7 +637,7 @@ fn not_found() -> Result> { .body( Full::new(Bytes::from("fastedge: Unknown app")) .map_err(|never| match never {}) - .boxed(), + .boxed_unsync(), )?) } @@ -575,14 +645,14 @@ fn not_found() -> Result> { fn too_many_requests() -> Result> { Ok(hyper::Response::builder() .status(StatusCode::TOO_MANY_REQUESTS) - .body(Empty::new().map_err(|never| match never {}).boxed())?) + .body(Empty::new().map_err(|never| match never {}).boxed_unsync())?) } /// Creates an HTTP 406 response. fn not_acceptable() -> Result> { Ok(hyper::Response::builder() .status(StatusCode::NOT_ACCEPTABLE) - .body(Empty::new().map_err(|never| match never {}).boxed())?) + .body(Empty::new().map_err(|never| match never {}).boxed_unsync())?) } #[derive(Debug, Clone)] @@ -725,6 +795,25 @@ mod tests { http::Request::builder().method("GET") } + // ── Stats exclusion by User-Agent ───────────────────────────────────── + + #[test_case(Some("scanner/1.0"), Some("scanner/1.0"), true; "exact match is excluded")] + #[test_case(Some("scanner/1.0"), Some("Scanner/1.0"), false; "case differs: not excluded")] + #[test_case(Some("scanner/1.0"), Some("scanner/1.0 "), false; "trailing space: not excluded")] + #[test_case(Some("scanner/1.0"), Some("x scanner/1.0"), false; "substring: not excluded")] + #[test_case(Some("scanner/1.0"), None, false; "no user agent: not excluded")] + #[test_case(None, Some("scanner/1.0"), false; "filter disabled: not excluded")] + fn stats_excluded_user_agent(excluded: Option<&str>, ua: Option<&str>, expected: bool) { + let mut headers = http::HeaderMap::new(); + if let Some(ua) = ua { + headers.insert(http::header::USER_AGENT, ua.parse().unwrap()); + } + assert_eq!( + crate::is_stats_excluded_user_agent(excluded, &headers), + expected + ); + } + // ── Name variant: server_name header ────────────────────────────────── #[test_case("app.server.com", "/", "app"; "server_name: normal subdomain")] @@ -737,7 +826,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); let app_name = assert_ok!(app_name_from_request(&req)); @@ -754,7 +843,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); let app_name = assert_ok!(app_name_from_request(&req)); @@ -771,7 +860,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); let app_name = assert_ok!(app_name_from_request(&req)); @@ -788,7 +877,7 @@ mod tests { empty_body_request().uri(uri).body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); let app_name = assert_ok!(app_name_from_request(&req)); @@ -806,7 +895,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); let app_name = assert_ok!(app_name_from_request(&req)); @@ -824,7 +913,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); let app_name = assert_ok!(app_name_from_request(&req)); @@ -840,7 +929,7 @@ mod tests { .body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); assert_err!(app_name_from_request(&req)); @@ -854,7 +943,7 @@ mod tests { empty_body_request().uri("/").body( Empty::::new() .map_err(|never| match never {}) - .boxed() + .boxed_unsync() ) ); assert_err!(app_name_from_request(&req)); @@ -873,4 +962,25 @@ mod tests { let id = AppName::Id(1234); assert_eq!("1234", id.to_string()); } + + // ── Error classification ───────────────────────────────────────────── + + /// A timeout trap must stay classified as a timeout even when the + /// `wasi_http` executor wraps it with `guest never invoked + /// `response-outparam::set``. Formatting the cause into the message instead + /// of chaining it loses the `Trap` downcast and yields 530 instead of 532. + #[test] + fn test_map_err_keeps_timeout_through_context() { + use anyhow::Context; + + let inner = anyhow::Error::from(wasmtime::Trap::Interrupt).context("error while executing"); + let wrapped = Err::<(), _>(inner) + .context("guest never invoked `response-outparam::set` method") + .unwrap_err(); + + let (status_code, fail_reason, _msg, internal_code) = crate::map_err(wrapped); + assert_eq!(crate::FASTEDGE_EXECUTION_TIMEOUT, status_code); + assert_eq!(runtime::AppResult::TIMEOUT as i32, fail_reason as i32); + assert_eq!(crate::INTERNAL_STATUS_TIMEOUT_INTERRUPT, internal_code); + } } diff --git a/crates/http-service/src/state.rs b/crates/http-service/src/state.rs index ad62f6f..944ded3 100644 --- a/crates/http-service/src/state.rs +++ b/crates/http-service/src/state.rs @@ -3,6 +3,7 @@ use http::request::Parts; use http::uri::Scheme; use http::{HeaderMap, HeaderName, HeaderValue, Uri, header}; use http_backend::Backend; +use http_backend::access_log::ExtRequestLogHandle; use http_backend::{is_backend_host, is_fastedge_internal_header, is_public_host}; use runtime::BackendRequest; use runtime::store::HasStats; @@ -31,6 +32,10 @@ const FASTEDGE_APP_ID: HeaderName = HeaderName::from_static("fastedge-app-id"); const CDN_LOOP: HeaderName = HeaderName::from_static("cdn-loop"); impl BackendRequest for HttpState { + fn ext_request_log_handle(&self) -> Option { + self.http_backend.ext_request_log_handle() + } + #[instrument(skip(self, head), level = "debug", ret)] fn backend_request(&mut self, mut head: Parts) -> anyhow::Result { match self.http_backend.strategy { diff --git a/crates/key-value-store/src/lib.rs b/crates/key-value-store/src/lib.rs index cff4c1f..745be29 100644 --- a/crates/key-value-store/src/lib.rs +++ b/crates/key-value-store/src/lib.rs @@ -5,6 +5,7 @@ use reactor::gcore::fastedge::key_value; use slab::Slab; use smol_str::SmolStr; use std::sync::Arc; +use std::sync::OnceLock; use tracing::instrument; use wasmtime::component::Resource; @@ -59,6 +60,37 @@ pub trait StoreManager: Sync + Send { ) -> Result, Error>; } +/// Command label passed to a [`ReadRetryObserver`]: `get` / `get_tracked`. +pub const CMD_GET: &str = "get"; +/// Command label passed to a [`ReadRetryObserver`]: `zrange_by_score`. +pub const CMD_ZRANGE_BY_SCORE: &str = "zrange_by_score"; +/// Command label passed to a [`ReadRetryObserver`]: `scan`. +pub const CMD_SCAN: &str = "scan"; +/// Command label passed to a [`ReadRetryObserver`]: `zscan`. +pub const CMD_ZSCAN: &str = "zscan"; +/// Command label passed to a [`ReadRetryObserver`]: `bf_exists`. +pub const CMD_BF_EXISTS: &str = "bf_exists"; + +/// Called once per retried backing-store read attempt (the retry itself, not +/// the first attempt), with the command label that was retried. +pub type ReadRetryObserver = fn(command: &'static str); + +static READ_RETRY_OBSERVER: OnceLock = OnceLock::new(); + +/// Install the process-wide read-retry observer. Lets an embedder count retries +/// (e.g. into a Prometheus counter) without this crate depending on a metrics +/// backend. Returns `false` if an observer was already installed. +pub fn set_read_retry_observer(observer: ReadRetryObserver) -> bool { + READ_RETRY_OBSERVER.set(observer).is_ok() +} + +/// Report one retried read attempt to the installed observer, if any. +pub fn note_read_retry(command: &'static str) { + if let Some(observer) = READ_RETRY_OBSERVER.get() { + observer(command); + } +} + /// Key-Value store read metrics pub trait ReadStats: Sync + Send { /// Increment key-value read count and size @@ -264,6 +296,27 @@ mod tests { use std::collections::HashMap; use std::sync::atomic::AtomicI32; + static OBSERVED_RETRIES: std::sync::atomic::AtomicUsize = + std::sync::atomic::AtomicUsize::new(0); + + #[test] + fn read_retry_observer_receives_retries() { + fn observer(command: &'static str) { + assert_eq!(command, CMD_GET); + OBSERVED_RETRIES.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + } + assert!(set_read_retry_observer(observer), "first install wins"); + // A second install is rejected rather than silently swapping the hook. + assert!(!set_read_retry_observer(observer)); + + note_read_retry(CMD_GET); + note_read_retry(CMD_GET); + assert_eq!( + OBSERVED_RETRIES.load(std::sync::atomic::Ordering::SeqCst), + 2 + ); + } + // Mock implementation of Store struct MockStore { data: HashMap, diff --git a/crates/key-value-store/src/redis_impl.rs b/crates/key-value-store/src/redis_impl.rs index 6ea80fd..92c5bcb 100644 --- a/crates/key-value-store/src/redis_impl.rs +++ b/crates/key-value-store/src/redis_impl.rs @@ -1,7 +1,7 @@ use crate::Store; use reactor::gcore::fastedge::key_value::{Error, Value}; use redis::aio::{ConnectionManager, ConnectionManagerConfig}; -use redis::{AsyncCommands, AsyncIter}; +use redis::{AsyncCommands, AsyncIter, RedisError, RetryMethod}; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Duration; @@ -24,17 +24,103 @@ const REDIS_READ_RETRIES: usize = 2; const REDIS_READ_RETRY_BASE_DELAY: Duration = Duration::from_millis(10); const REDIS_READ_RETRY_MAX_DELAY: Duration = Duration::from_millis(50); +/// Backoff applied between attempts when the server asked us to wait. +/// Saturating throughout, so a larger retry budget can never overflow the +/// shift or the multiplication into a panic. fn redis_read_retry_delay(attempt: usize) -> Duration { - let scale = 1u32 << (attempt.saturating_sub(1) as u32); - (REDIS_READ_RETRY_BASE_DELAY * scale).min(REDIS_READ_RETRY_MAX_DELAY) + let scale = 1u32 + .checked_shl(attempt.saturating_sub(1) as u32) + .unwrap_or(u32::MAX); + REDIS_READ_RETRY_BASE_DELAY + .saturating_mul(scale) + .min(REDIS_READ_RETRY_MAX_DELAY) +} + +/// How long to wait before re-attempting a failed read, or `None` when the +/// failure must not be retried at all. `attempt` is the 1-based number of the +/// attempt about to be made, used to scale the backoff. +/// +/// Allowlist rather than denylist: [`RetryMethod`] is `#[non_exhaustive]`, so a +/// redis-crate bump can introduce a variant, and defaulting those to "retry" +/// would silently spend the budget on failures we have never classified. +/// +/// The cluster redirect methods (`MovedRedirect`, `AskRedirect`, +/// `RefreshSlotsAndRetry`, `ReconnectFromInitialConnections`) are deliberately +/// excluded: this store speaks to Redis through a plain `ConnectionManager`, +/// not the cluster API, so nothing here can follow a redirect — a bare retry +/// would just re-send to the same node and collect the same error. +fn retry_backoff(error: &RedisError, attempt: usize) -> Option { + match error.retry_method() { + // Nothing to back off from: the command never reached a working + // server, so there is no load to relieve and sleeping would only add + // latency to the request. `Reconnect` means the socket is gone or the + // stream desynced — re-running the op picks the next pooled connection + // while `ConnectionManager` re-establishes its own socket underneath + // (with its own capped backoff), so the next attempt already gets a + // fresh channel. + RetryMethod::RetryImmediately | RetryMethod::Reconnect => Some(Duration::ZERO), + // The server is reachable but told us it cannot serve yet (`LOADING`, + // `TRYAGAIN`). Here the pause is the point: it keeps a struggling node + // from being hammered by every in-flight request at once. + RetryMethod::WaitAndRetry => Some(redis_read_retry_delay(attempt)), + _ => None, + } +} + +/// Whether the server rejected the command in a way that can never succeed on +/// retry, because the guest's own request was at fault — e.g. `WRONGTYPE` when +/// `get` addresses a key holding a sorted set, or `NOPERM`/`ERR` for a command +/// the guest may not run. +/// +/// Such a rejection says nothing about node or Redis health, so it must not +/// raise a node-level warning; it is handed back to the guest instead. +/// +/// Distinct from `!should_retry`: a `MOVED` reply or a failed authentication is +/// equally pointless to retry, but it is the node's problem, not the app's, so +/// it stays an opaque internal error and still warns. +/// +/// Only failures the server itself answered are classified: [`RedisError::code`] +/// is `None` for I/O, timeout and client-config failures. +fn is_guest_fault(error: &RedisError) -> bool { + matches!(error.retry_method(), RetryMethod::NoRetry) && error.code().is_some() +} + +/// The server's own code and detail, for handing back to the guest. +fn guest_fault_message(error: &RedisError) -> String { + let code = error.code().unwrap_or_default(); + match error.detail() { + Some(detail) => format!("{code}: {detail}"), + None => code.to_string(), + } +} + +/// Map a failed read onto the error the guest sees. +/// +/// A guest-caused rejection is returned verbatim as [`Error::Other`] so the app +/// can fix its request, and is only traced at debug level. Anything else is +/// opaque to the guest and is reported by `log_unexpected`, which each caller +/// supplies so the warning keeps that command's own fields. +fn read_error(error: RedisError, log_unexpected: impl FnOnce(&RedisError)) -> Error { + if is_guest_fault(&error) { + tracing::debug!(cause = ?error, "kv-store: redis rejected read"); + return Error::Other(guest_fault_message(&error)); + } + log_unexpected(&error); + Error::InternalError } /// Retry a read command up to `REDIS_READ_RETRIES` times with capped /// exponential backoff. `op` is re-invoked from scratch on each attempt so it /// can pick a fresh pooled connection (used by `get`/`zrange_by_score`/ /// `bf_exists`; `scan`/`zscan` retry the same connection since their iterator -/// borrows it across the loop). -async fn retry_read(mut op: F) -> Result +/// borrows it across the loop). Each retry is reported to the installed +/// [`crate::ReadRetryObserver`] under `command`. +/// +/// Only failures [`retry_backoff`] accepts are attempted again, and only those +/// that ask for it sleep first; deterministic failures (a `WRONGTYPE` key, a +/// `MOVED` slot) return on the first error rather than spending the budget on a +/// result that cannot change. +async fn retry_read(command: &'static str, mut op: F) -> Result where F: FnMut() -> Fut, Fut: std::future::Future>, @@ -43,12 +129,20 @@ where loop { match op().await { Ok(value) => return Ok(value), - Err(error) if attempt < REDIS_READ_RETRIES => { + Err(error) => { + if attempt >= REDIS_READ_RETRIES { + return Err(error); + } + let Some(delay) = retry_backoff(&error, attempt + 1) else { + return Err(error); + }; attempt += 1; + crate::note_read_retry(command); tracing::debug!(attempt, cause = ?error, "kv-store: redis read retry"); - tokio::time::sleep(redis_read_retry_delay(attempt)).await; + if !delay.is_zero() { + tokio::time::sleep(delay).await; + } } - Err(error) => return Err(error), } } } @@ -115,11 +209,12 @@ impl RedisStore { #[async_trait::async_trait] impl Store for RedisStore { async fn get(&self, key: &str) -> Result, Error> { - retry_read(|| async { self.conn().get(key).await }) + retry_read(crate::CMD_GET, || async { self.conn().get(key).await }) .await .map_err(|error| { - tracing::warn!(cause = ?error, key, "kv-store: redis get"); - Error::InternalError + read_error(error, |error| { + tracing::warn!(cause = ?error, key, "kv-store: redis get"); + }) }) } @@ -129,12 +224,15 @@ impl Store for RedisStore { min: f64, max: f64, ) -> Result, Error> { - retry_read(|| async { self.conn().zrangebyscore_withscores(key, min, max).await }) - .await - .map_err(|error| { + retry_read(crate::CMD_ZRANGE_BY_SCORE, || async { + self.conn().zrangebyscore_withscores(key, min, max).await + }) + .await + .map_err(|error| { + read_error(error, |error| { tracing::warn!(cause = ?error, key, min, max, "kv-store: redis zrangebyscore"); - Error::InternalError }) + }) } async fn scan(&self, pattern: &str) -> Result, Error> { @@ -143,22 +241,32 @@ impl Store for RedisStore { let mut it = loop { match conn.scan_match(pattern).await { Ok(it) => break it, - Err(error) if attempt < REDIS_READ_RETRIES => { + Err(error) => { + let backoff = if attempt < REDIS_READ_RETRIES { + retry_backoff(&error, attempt + 1) + } else { + None + }; + let Some(delay) = backoff else { + return Err(read_error(error, |error| { + tracing::warn!(cause = ?error, pattern, "kv-store: redis scan_match"); + })); + }; attempt += 1; + crate::note_read_retry(crate::CMD_SCAN); tracing::debug!(attempt, cause = ?error, pattern, "kv-store: redis scan_match retry"); - tokio::time::sleep(redis_read_retry_delay(attempt)).await; - } - Err(error) => { - tracing::warn!(cause = ?error, pattern, "kv-store: redis scan_match"); - return Err(Error::InternalError); + if !delay.is_zero() { + tokio::time::sleep(delay).await; + } } } }; let mut ret = vec![]; while let Some(element) = it.next_item().await { ret.push(element.map_err(|error| { - tracing::warn!(cause = ?error, pattern, "kv-store: redis scan_match: item"); - Error::Other(error.to_string()) + read_error(error, |error| { + tracing::warn!(cause = ?error, pattern, "kv-store: redis scan_match: item"); + }) })?); } Ok(ret) @@ -170,29 +278,39 @@ impl Store for RedisStore { let mut it: AsyncIter<(Value, f64)> = loop { match conn.zscan_match(key, pattern).await { Ok(it) => break it, - Err(error) if attempt < REDIS_READ_RETRIES => { + Err(error) => { + let backoff = if attempt < REDIS_READ_RETRIES { + retry_backoff(&error, attempt + 1) + } else { + None + }; + let Some(delay) = backoff else { + return Err(read_error(error, |error| { + tracing::warn!(cause = ?error, key, pattern, "kv-store: redis zscan_match"); + })); + }; attempt += 1; + crate::note_read_retry(crate::CMD_ZSCAN); tracing::debug!(attempt, cause = ?error, key, pattern, "kv-store: redis zscan_match retry"); - tokio::time::sleep(redis_read_retry_delay(attempt)).await; - } - Err(error) => { - tracing::warn!(cause = ?error, key, pattern, "kv-store: redis zscan_match"); - return Err(Error::InternalError); + if !delay.is_zero() { + tokio::time::sleep(delay).await; + } } } }; let mut ret = vec![]; while let Some(element) = it.next_item().await { ret.push(element.map_err(|error| { - tracing::warn!(cause = ?error, key, pattern, "kv-store: redis zscan_match: item"); - Error::Other(error.to_string()) + read_error(error, |error| { + tracing::warn!(cause = ?error, key, pattern, "kv-store: redis zscan_match: item"); + }) })?); } Ok(ret) } async fn bf_exists(&self, key: &str, item: &str) -> Result { - retry_read(|| async { + retry_read(crate::CMD_BF_EXISTS, || async { redis::cmd("BF.EXISTS") .arg(key) .arg(item) @@ -201,8 +319,219 @@ impl Store for RedisStore { }) .await .map_err(|error| { - tracing::warn!(cause = ?error, key, item, "kv-store: redis bf_exists"); - Error::InternalError + read_error(error, |error| { + tracing::warn!(cause = ?error, key, item, "kv-store: redis bf_exists"); + }) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::AtomicUsize; + + /// Build a real `RedisError` by parsing a RESP error frame, so the tests + /// exercise the same classification path as a live server reply. + fn server_error(frame: &str) -> RedisError { + match ::redis::parse_redis_value(frame.as_bytes()) { + Ok(::redis::Value::ServerError(error)) => error.into(), + other => panic!("RESP error frame must parse as a server error: {other:?}"), + } + } + + #[test] + fn wrongtype_is_a_guest_fault() { + let error = + server_error("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"); + assert!(is_guest_fault(&error)); + assert_eq!( + guest_fault_message(&error), + "WRONGTYPE: Operation against a key holding the wrong kind of value" + ); + } + + #[test] + fn other_permanent_server_rejections_are_guest_faults() { + for frame in [ + "-ERR wrong number of arguments for 'get' command\r\n", + "-NOPERM this user has no permissions to run the 'get' command\r\n", + ] { + assert!(is_guest_fault(&server_error(frame)), "frame: {frame}"); + } + } + + #[test] + fn retriable_server_errors_are_not_guest_faults() { + // LOADING/TRYAGAIN are transient: the same command can succeed later, + // so they must keep retrying and keep warning. + for frame in [ + "-LOADING Redis is loading the dataset in memory\r\n", + "-TRYAGAIN Multiple keys request during rehashing of slot\r\n", + ] { + let error = server_error(frame); + assert!(!is_guest_fault(&error), "frame: {frame}"); + // The server asked us to wait, so the pause is the point. + assert_eq!( + retry_backoff(&error, 1), + Some(REDIS_READ_RETRY_BASE_DELAY), + "frame: {frame}" + ); + } + } + + #[test] + fn deterministic_errors_are_not_retried() { + for frame in [ + "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n", + "-ERR wrong number of arguments for 'get' command\r\n", + "-NOPERM this user has no permissions to run the 'get' command\r\n", + // Cluster redirects: this store uses a plain (non-cluster) client, + // so a bare retry can only re-collect the same reply. + "-MOVED 3999 127.0.0.1:6381\r\n", + "-ASK 3999 127.0.0.1:6381\r\n", + ] { + assert_eq!( + retry_backoff(&server_error(frame), 1), + None, + "frame: {frame}" + ); + } + } + + #[test] + fn cluster_redirects_are_node_problems_not_guest_faults() { + // Not retriable, but the app did nothing wrong: must stay opaque and + // still warn, unlike WRONGTYPE. + let error = server_error("-MOVED 3999 127.0.0.1:6381\r\n"); + assert_eq!(retry_backoff(&error, 1), None); + assert!(!is_guest_fault(&error)); + + let mut warned = false; + let mapped = read_error(error, |_| warned = true); + assert!(warned); + assert!(matches!(mapped, Error::InternalError)); + } + + #[test] + fn connection_errors_are_retried_but_are_not_guest_faults() { + // No server reply at all, so there is no code to hand back. + let error = RedisError::from(std::io::Error::from(std::io::ErrorKind::ConnectionReset)); + assert!(!is_guest_fault(&error)); + // A dead socket is not congestion: reconnect and re-issue at once + // rather than adding latency to a request already on the hot path. + assert_eq!(retry_backoff(&error, 1), Some(Duration::ZERO)); + } + + #[test] + fn wait_and_retry_backoff_grows_and_is_capped() { + let error = server_error("-LOADING Redis is loading the dataset in memory\r\n"); + assert_eq!(retry_backoff(&error, 1), Some(REDIS_READ_RETRY_BASE_DELAY)); + assert_eq!( + retry_backoff(&error, 2), + Some(REDIS_READ_RETRY_BASE_DELAY * 2) + ); + assert_eq!(retry_backoff(&error, 99), Some(REDIS_READ_RETRY_MAX_DELAY)); + } + + #[test] + fn guest_fault_is_returned_verbatim_without_warning() { + let error = + server_error("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"); + let mut warned = false; + let mapped = read_error(error, |_| warned = true); + + assert!(!warned, "a guest-caused rejection must not warn"); + assert!( + matches!(mapped, Error::Other(message) if message.starts_with("WRONGTYPE: ")), + "guest must receive the server's own message" + ); + } + + #[test] + fn unexpected_error_warns_and_stays_opaque() { + let error = RedisError::from(std::io::Error::from(std::io::ErrorKind::ConnectionReset)); + let mut warned = false; + let mapped = read_error(error, |_| warned = true); + + assert!(warned); + assert!(matches!(mapped, Error::InternalError)); + } + + #[tokio::test] + async fn retry_read_does_not_retry_a_guest_fault() { + let attempts = AtomicUsize::new(0); + let result: Result<(), _> = retry_read(crate::CMD_GET, || async { + attempts.fetch_add(1, Ordering::Relaxed); + Err(server_error( + "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n", + )) + }) + .await; + + assert!(result.is_err()); + assert_eq!( + attempts.load(Ordering::Relaxed), + 1, + "a deterministic rejection must not burn the retry budget" + ); + } + + #[tokio::test] + async fn retry_read_does_not_retry_a_cluster_redirect() { + let attempts = AtomicUsize::new(0); + let result: Result<(), _> = retry_read(crate::CMD_GET, || async { + attempts.fetch_add(1, Ordering::Relaxed); + Err(server_error("-MOVED 3999 127.0.0.1:6381\r\n")) }) + .await; + + assert!(result.is_err()); + assert_eq!(attempts.load(Ordering::Relaxed), 1); + } + + #[tokio::test] + async fn retry_read_still_retries_transient_errors() { + let attempts = AtomicUsize::new(0); + let started = std::time::Instant::now(); + let result: Result<(), _> = retry_read(crate::CMD_GET, || async { + attempts.fetch_add(1, Ordering::Relaxed); + Err(RedisError::from(std::io::Error::from( + std::io::ErrorKind::ConnectionReset, + ))) + }) + .await; + + assert!(result.is_err()); + assert_eq!( + attempts.load(Ordering::Relaxed), + REDIS_READ_RETRIES + 1, + "transient failures must still use the full retry budget" + ); + assert!( + started.elapsed() < REDIS_READ_RETRY_BASE_DELAY, + "a dead socket must be re-issued without sleeping, took {:?}", + started.elapsed() + ); + } + + #[tokio::test] + async fn retry_read_sleeps_only_when_the_server_asks_to_wait() { + let attempts = AtomicUsize::new(0); + let started = std::time::Instant::now(); + let result: Result<(), _> = retry_read(crate::CMD_GET, || async { + attempts.fetch_add(1, Ordering::Relaxed); + Err(server_error( + "-LOADING Redis is loading the dataset in memory\r\n", + )) + }) + .await; + + assert!(result.is_err()); + assert_eq!(attempts.load(Ordering::Relaxed), REDIS_READ_RETRIES + 1); + assert!( + started.elapsed() >= REDIS_READ_RETRY_BASE_DELAY, + "a loading node must be given time to recover" + ); } } diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index d65243c..6bb6bb2 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -5,16 +5,12 @@ edition.workspace = true publish.workspace = true authors.workspace = true -[features] -metrics = ["prometheus", "lazy_static"] - [dependencies] anyhow = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } -wasi-common = { workspace = true } wasmtime-wasi-nn = { workspace = true } wasmtime-wasi-http = { workspace = true } wasmtime-environ = { workspace = true } @@ -22,6 +18,8 @@ wasmtime-wasi-io = { workspace = true } smol_str = { workspace = true } moka = { workspace = true } http = { workspace = true } +http-body = "1" +http-body-util = "0.1" wit-component = "0.246.2" tracing = { workspace = true } bytesize = { workspace = true } @@ -32,11 +30,10 @@ utils = { path = "../utils" } secret = { path = "../secret" } async-trait = "0.1" bytes = "1.11" +rand = "0.8" serde = "1.0" serde_json = "1.0" chrono = { version = "0.4", features = ["serde"] } -prometheus = { version = "0.14.0", features = ["process"], optional = true } -lazy_static = { version = "1.5.0", optional = true } [dev-dependencies] claims = "0.8" diff --git a/crates/runtime/src/access_log.rs b/crates/runtime/src/access_log.rs new file mode 100644 index 0000000..0ab6d4b --- /dev/null +++ b/crates/runtime/src/access_log.rs @@ -0,0 +1,170 @@ +//! Per-request system log. +//! +//! This is **not** the guest application log (that ships the WASM app's stdout +//! via [`crate::logger`]). This log emits exactly one record per processed +//! request describing how the edge server handled it — traceparent, final HTTP +//! status, the `X-CDN-Internal-Status` diagnostic code, timing, etc. — for both +//! successful and failed requests, across every execution path (ProxyWasm, +//! FastEdge http-handler, and standard wasi:http). +//! +//! The transport is intentionally abstracted behind [`AccessLogSender`]. The +//! concrete appender (planned: a system log shipped over UDP with a JSON +//! payload) is implemented separately; until one is wired in, the runtime uses +//! [`NoopAccessLogSender`]. + +use std::time::Duration; + +/// Which execution path served the request. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RequestKind { + /// ProxyWasm protocol (v1/v2/v2a) — WASI Preview1 core modules. + ProxyWasm, + /// HTTP request served by a Preview2 component — both the FastEdge + /// `gcore:fastedge/http-handler` export and the standard + /// `wasi:http/incoming-handler` export are reported under this variant. + Http, + /// Outbound HTTP request the guest made while serving a request (ProxyWasm + /// `proxy_http_call` or the WASI/http-handler fetch client). Emitted as its + /// own record, correlated to the inbound request via `traceparent`. + ExtHttp, +} + +impl RequestKind { + /// Stable lowercase identifier, suitable for a log field. + pub fn as_str(&self) -> &'static str { + match self { + RequestKind::ProxyWasm => "cdn", + RequestKind::Http => "http", + RequestKind::ExtHttp => "fetch", + } + } +} + +/// ProxyWasm lifecycle callback ("trigger") that produced this record. +/// +/// A single ProxyWasm request is driven through several host callbacks; each +/// one is handled — and logged — independently. Only set on the ProxyWasm path; +/// the HTTP paths run a single execution and leave it `None`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Trigger { + RequestHeaders, + ResponseHeaders, + RequestBody, + ResponseBody, + Log, +} + +impl Trigger { + /// Short, stable identifier for the ProxyWasm callback (kept compact to + /// minimise datagram size): `rqh`/`rsh` = request/response headers, + /// `rqb`/`rsb` = request/response body, `log` = on_log. + pub fn as_str(&self) -> &'static str { + match self { + Trigger::RequestHeaders => "rqh", + Trigger::ResponseHeaders => "rsh", + Trigger::RequestBody => "rqb", + Trigger::ResponseBody => "rsb", + Trigger::Log => "log", + } + } +} + +/// Upstream service that initiated a ProxyWasm request. Derived from the +/// ProxyWasm protocol version: nginx speaks v1/v2a/v2b and the v3 yamux +/// dialect, core-proxy speaks v2/v2c/v2d. Only set on the ProxyWasm path. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Initiator { + Nginx, + CoreProxy, +} + +impl Initiator { + /// Stable identifier, suitable for a log field. + pub fn as_str(&self) -> &'static str { + match self { + Initiator::Nginx => "nginx", + Initiator::CoreProxy => "core-proxy", + } + } +} + +/// One log record describing the processing of a single request. +/// +/// `traceparent` and `status_code` are the primary fields; the rest are +/// best-effort context that may be absent on early-return paths (e.g. an +/// unknown application is rejected before its config — and thus its ids — are +/// known). +#[derive(Debug, Clone)] +pub struct AccessLog { + /// W3C trace-context id used to correlate the request across systems. + pub traceparent: String, + /// Execution path that handled the request. + pub kind: RequestKind, + /// Final HTTP status code returned to the client. + pub status_code: u16, + /// `X-CDN-Internal-Status` diagnostic code (range 3000–3999); `0` when the + /// request completed without an internal error. + pub internal_status_code: u16, + /// Edge application id, once resolved. + pub app_id: Option, + /// Edge application name, once resolved. + pub app_name: Option, + /// Owning customer id, once resolved. + pub client_id: Option, + /// Request HTTP method, when cheaply available. + pub method: Option, + /// Request URI, when cheaply available. + pub uri: Option, + /// Wall-clock time spent executing the WASM app, when the app ran. + pub duration: Option, + /// ProxyWasm lifecycle callback that produced this record (ProxyWasm only). + pub trigger: Option, + /// Upstream service that initiated the request (ProxyWasm only). + pub initiator: Option, +} + +impl AccessLog { + /// Start a record for a request of the given `kind`, keyed by `traceparent`. + /// Remaining fields are filled in as they become known during processing. + pub fn new(traceparent: String, kind: RequestKind) -> Self { + Self { + traceparent, + kind, + status_code: 0, + internal_status_code: 0, + app_id: None, + app_name: None, + client_id: None, + method: None, + uri: None, + duration: None, + trigger: None, + initiator: None, + } + } + + /// A request is considered successful when no internal error code was set + /// (client errors such as 404/429 are not internal failures). + pub fn success(&self) -> bool { + self.internal_status_code == 0 + } +} + +/// Sink for [`AccessLog`] records. +/// +/// Implementations must be non-blocking / fire-and-forget: `log` is called on +/// the request hot path and must not await or block on I/O. The planned UDP +/// appender simply sends a datagram (or hands off to a background task). +pub trait AccessLogSender: Send + Sync { + /// Record one completed request. Must not block. + fn log(&self, record: AccessLog); +} + +/// A [`AccessLogSender`] that discards every record. Used as the default until +/// a concrete appender is configured. +#[derive(Debug, Default, Clone, Copy)] +pub struct NoopAccessLogSender; + +impl AccessLogSender for NoopAccessLogSender { + fn log(&self, _record: AccessLog) {} +} diff --git a/crates/runtime/src/adapters/wasi_snapshot_preview1.reactor.wasm b/crates/runtime/src/adapters/wasi_snapshot_preview1.reactor.wasm index 89b68cf..ed39fa8 100644 Binary files a/crates/runtime/src/adapters/wasi_snapshot_preview1.reactor.wasm and b/crates/runtime/src/adapters/wasi_snapshot_preview1.reactor.wasm differ diff --git a/crates/runtime/src/app.rs b/crates/runtime/src/app.rs index ee5acde..e25e4f4 100644 --- a/crates/runtime/src/app.rs +++ b/crates/runtime/src/app.rs @@ -44,6 +44,10 @@ pub struct KvStoreOption { pub name: SmolStr, #[serde(default)] pub prefix: SmolStr, + /// Maximum **bytes** held by this store's in-process value cache (keys, + /// values and per-entry overhead), not a number of entries: entry count + /// cannot bound memory, since the same 25 000 entries are 2 MiB of + /// 64-byte values or 200 MiB of 8 KiB ones. #[serde(default = "KvStoreOption::default_cache_size")] pub cache_size: u64, #[serde(default = "KvStoreOption::default_cache_ttl")] @@ -51,8 +55,11 @@ pub struct KvStoreOption { } impl KvStoreOption { + /// 10 MiB. Was 25 000 *entries* until 2026-09-15; the unit changed with + /// the cache's weigher, and the field was set nowhere in prod or preprod, + /// so every store simply takes this default. fn default_cache_size() -> u64 { - 25000 + 10 * 1024 * 1024 } fn default_cache_ttl() -> u64 { @@ -306,7 +313,7 @@ mod tests { assert_eq!(kv.param, ""); assert_eq!(kv.name, "store"); assert_eq!(kv.prefix, "pre"); - assert_eq!(kv.cache_size, 25000); + assert_eq!(kv.cache_size, 10 * 1024 * 1024); assert_eq!(kv.cache_ttl, 60); } @@ -316,14 +323,14 @@ mod tests { "param": "url2", "name": "store2", "prefix": "pre2", - "cache_size": 5000, + "cache_size": 5000000, "cache_ttl": 120 }"#; let kv: KvStoreOption = serde_json::from_str(json).unwrap(); assert_eq!(kv.param, "url2"); assert_eq!(kv.name, "store2"); assert_eq!(kv.prefix, "pre2"); - assert_eq!(kv.cache_size, 5000); + assert_eq!(kv.cache_size, 5000000); assert_eq!(kv.cache_ttl, 120); } diff --git a/crates/runtime/src/instances.rs b/crates/runtime/src/instances.rs index 9dd1e89..ddbd0e2 100644 --- a/crates/runtime/src/instances.rs +++ b/crates/runtime/src/instances.rs @@ -14,78 +14,79 @@ //! through `StoreBuilder::build` — ProxyWasm, `http-handler`, `wasi:http` — is counted //! against the same pool it actually draws from. There is deliberately no `executor` label: //! the pools are per-`Engine` and shared, so only the total is meaningful for sizing. +//! +//! The counters are plain atomics; embedders export them (e.g. as the Prometheus gauges +//! `fastedge_wasm_instances_live` / `fastedge_wasm_instances_peak`) by calling +//! [`flush_peak_and_live`] once per scrape. That returns both values from a single `live` +//! sample, which is what keeps the exported peak from ever reading below the exported +//! live count; reading [`live`] and [`flush_peak`] separately does not. + +use std::sync::atomic::{AtomicI64, Ordering}; + +/// Currently live instances. +static LIVE: AtomicI64 = AtomicI64::new(0); + +/// Running max since the previous [`flush_peak`]. Kept separately so the peak can be +/// updated with an atomic `fetch_max` and drained with `swap(0)` on scrape. A plain +/// scrape-time sample of [`LIVE`] would miss the sub-second concurrency spikes that a +/// stalled backend produces — exactly the peaks the pool has to be sized for. +static PEAK: AtomicI64 = AtomicI64::new(0); + +fn acquire() { + // `fetch_add` returns the previous value, so `+ 1` is the level that this + // acquisition genuinely produced — a valid sample for the peak. + let live = LIVE.fetch_add(1, Ordering::Relaxed) + 1; + PEAK.fetch_max(live, Ordering::Relaxed); +} -#[cfg(feature = "metrics")] -mod imp { - use lazy_static::lazy_static; - use prometheus::{IntGauge, register_int_gauge}; - use std::sync::atomic::{AtomicI64, Ordering}; - - lazy_static! { - static ref WASM_INSTANCES_LIVE: IntGauge = register_int_gauge!( - "fastedge_wasm_instances_live", - "WASM instances currently alive, each holding one slot in every wasmtime pooling-allocator pool" - ) - .unwrap(); - - /// Peak since the previous scrape. A plain gauge is sampled at the scrape - /// interval and will miss the sub-second concurrency spikes that a stalled backend - /// produces — exactly the peaks the pool has to be sized for. This one cannot. - static ref WASM_INSTANCES_PEAK: IntGauge = register_int_gauge!( - "fastedge_wasm_instances_peak", - "Highest number of concurrently live WASM instances observed since the \ - previous scrape (resets on scrape)" - ) - .unwrap(); - } - - /// Running max since the previous scrape, kept separately so the peak can be updated - /// with an atomic `fetch_max` — `IntGauge` only offers `set`, which cannot express - /// "raise to" — and drained with `swap(0)` by [`flush_peak`] on scrape. - static PEAK: AtomicI64 = AtomicI64::new(0); - - pub(super) fn acquire() { - WASM_INSTANCES_LIVE.inc(); - // Reading back after `inc` may observe another thread's concurrent increment. That - // is still a level that genuinely occurred, so it is a valid sample for the peak. - let live = WASM_INSTANCES_LIVE.get(); - PEAK.fetch_max(live, Ordering::Relaxed); - } - - pub(super) fn release() { - WASM_INSTANCES_LIVE.dec(); - } +fn release() { + LIVE.fetch_sub(1, Ordering::Relaxed); +} - /// Currently live instances. Test/diagnostic accessor. - pub fn live() -> i64 { - WASM_INSTANCES_LIVE.get() - } +/// Currently live instances. +pub fn live() -> i64 { + LIVE.load(Ordering::Relaxed) +} - /// Peak since the previous [`flush_peak`]. Test/diagnostic accessor. - pub fn peak() -> i64 { - PEAK.load(Ordering::Relaxed) - } +/// Peak since the previous [`flush_peak`]. Test/diagnostic accessor. +pub fn peak() -> i64 { + PEAK.load(Ordering::Relaxed) +} - /// Export and reset the peak gauge; call on each Prometheus scrape. Never reports - /// less than the currently live count, so a long-running steady load can't read as 0. - pub fn flush_peak() { - let peak = PEAK - .swap(0, Ordering::Relaxed) - .max(WASM_INSTANCES_LIVE.get()); - WASM_INSTANCES_PEAK.set(peak); - } +/// Drain and return the peak since the previous call; invoke on each metrics scrape. +/// Never reports less than the currently live count, so a long-running steady load +/// can't read as 0. +/// +/// Prefer [`flush_peak_and_live`] when exporting both gauges: reading `live` separately +/// can observe a *later*, higher level than the one folded in here and so publish a +/// scrape where peak < live. +pub fn flush_peak() -> i64 { + let (peak, _live) = flush_peak_and_live(); + peak } -#[cfg(not(feature = "metrics"))] -mod imp { - pub(super) fn acquire() {} - pub(super) fn release() {} - pub fn flush_peak() {} +/// Drain the peak and sample the live count for a single scrape, returning +/// `(peak, live)`. +/// +/// `live` is sampled *before* the peak is drained and is the same value folded into the +/// peak, so the returned pair always satisfies `peak >= live`. Sampling the two +/// independently does not: an instance acquired between the two reads raises `live` +/// above a peak that was already computed, publishing a scrape that contradicts the +/// documented "never less than currently live" semantics. +pub fn flush_peak_and_live() -> (i64, i64) { + // Exactly one `live()` read: taking the sample here and threading it through is what + // makes `peak >= live` structural rather than timing-dependent. + drain_peak_against(live()) } -pub use imp::flush_peak; -#[cfg(feature = "metrics")] -pub use imp::{live, peak}; +/// Drain `PEAK` and fold `live_sample` into it, returning `(peak, live_sample)`. +/// +/// Both halves of the returned pair derive from the single caller-supplied sample, so +/// `peak >= live` holds for any input — including a fully drained `PEAK`. +fn drain_peak_against(live_sample: i64) -> (i64, i64) { + let peak = PEAK.swap(0, Ordering::Relaxed).max(live_sample); + (peak, live_sample) +} /// RAII counter for one live WASM instance. /// @@ -97,7 +98,7 @@ pub struct LiveInstanceGuard; impl LiveInstanceGuard { pub fn new() -> Self { - imp::acquire(); + acquire(); LiveInstanceGuard } } @@ -110,18 +111,29 @@ impl Default for LiveInstanceGuard { impl Drop for LiveInstanceGuard { fn drop(&mut self) { - imp::release(); + release(); } } -#[cfg(all(test, feature = "metrics"))] +#[cfg(test)] mod tests { use super::*; + use std::sync::{Mutex, MutexGuard, OnceLock}; + + /// `LIVE` / `PEAK` are process-global, so a test that drains the peak must not run + /// alongside one asserting it is monotonic. Serialise every test that touches them. + fn serial() -> MutexGuard<'static, ()> { + static LOCK: OnceLock> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())) + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + } - /// Guards from concurrent tests share the process-global gauge, so assertions are on + /// Guards from concurrent tests share the process-global counter, so assertions are on /// deltas rather than absolute values. #[test] fn guard_tracks_live_count_and_raises_peak() { + let _serial = serial(); let before = live(); let first = LiveInstanceGuard::new(); @@ -139,4 +151,45 @@ mod tests { // The high-water mark is monotonic: dropping instances must not lower it. assert_eq!(peak(), peak_at_top); } + + /// The core invariant, independent of timing: whatever live level a scrape samples, + /// the peak reported alongside it is never lower — even when `PEAK` was just drained + /// and contributes nothing. This is the case the old two-read scrape got wrong: it + /// computed the peak from one `live()` read and published a second, later one. + #[test] + fn drained_peak_still_reports_at_least_the_live_sample() { + let _serial = serial(); + for live_sample in [0, 1, 7, 1_000] { + PEAK.store(0, Ordering::Relaxed); + let (peak, live) = drain_peak_against(live_sample); + assert_eq!(live, live_sample); + assert!(peak >= live, "peak {peak} < live {live}"); + } + } + + /// A recorded spike still wins when it exceeds the live level at scrape time. + #[test] + fn recorded_spike_outranks_a_lower_live_sample() { + let _serial = serial(); + PEAK.store(50, Ordering::Relaxed); + let (peak, live) = drain_peak_against(3); + assert_eq!((peak, live), (50, 3)); + // Draining is destructive: the next scrape falls back to the live sample. + let (peak, _) = drain_peak_against(3); + assert_eq!(peak, 3); + } + + /// End-to-end through the real globals: acquiring after a drain must not produce a + /// scrape where peak < live. + #[test] + fn flush_peak_and_live_holds_invariant_after_a_drain() { + let _serial = serial(); + let _first = LiveInstanceGuard::new(); + // Drain, so `PEAK` is 0 and only the live sample can carry the invariant. + let _ = flush_peak_and_live(); + + let _second = LiveInstanceGuard::new(); + let (peak, live) = flush_peak_and_live(); + assert!(peak >= live, "peak {peak} < live {live}"); + } } diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index bdbcb6e..8e7ff6e 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -1,15 +1,21 @@ use crate::app::KvStoreOption; use crate::store::HasStats; +use http_backend::access_log::ExtRequestLogHandle; use http_backend::stats::ExtStatsTimer; use std::net::Ipv4Addr; -use std::sync::Arc; +use std::pin::Pin; use std::sync::atomic::{AtomicU64, Ordering}; -use std::time::Instant; +use std::sync::{Arc, OnceLock}; +use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; use std::{fmt::Debug, ops::Deref}; use utils::{Dictionary, Utils}; use wasmtime_wasi::ResourceTable; use wasmtime_wasi::WasiCtxView; -use wasmtime_wasi_http::{HttpResult, WasiHttpCtx, WasiHttpView}; +use wasmtime_wasi_http::{ + DEFAULT_FORBIDDEN_HEADERS, RequestOptions, WasiBody, WasiHttpCtx, WasiHttpCtxView, + WasiHttpHooks, WasiHttpView, default_send_request, +}; use wasmtime_wasi_io::IoView; use crate::store::StoreBuilder; @@ -22,6 +28,7 @@ use wasmtime::{ }; use wit_component::ComponentEncoder; +pub mod access_log; pub mod app; pub mod instances; mod limiter; @@ -30,48 +37,55 @@ mod registry; pub mod service; pub mod store; pub mod stub; +pub mod trace; pub mod util; +pub use trace::new_traceparent; + +use crate::access_log::{AccessLogSender, NoopAccessLogSender}; use crate::app::SecretOption; use crate::logger::Logger; use crate::util::stats::StatsVisitor; use anyhow::{anyhow, bail}; pub use app::{App, SecretValue, SecretValues}; +use bytes::Bytes; use http::request::Parts; -use http::{HeaderName, Request, header}; +use http::{HeaderName, Request, Response, header}; +use http_body::{Body, Frame, SizeHint}; +use http_body_util::BodyExt; use secret::SecretStore; use smol_str::SmolStr; use std::borrow::Cow; +use std::future::Future; use wasmtime_environ::wasmparser::{Encoding, Parser, Payload}; -use wasmtime_wasi_http::body::HyperOutgoingBody; -use wasmtime_wasi_http::{ - bindings::http::types::ErrorCode, - types::{HostFutureIncomingResponse, OutgoingRequestConfig, default_send_request_handler}, -}; use wasmtime_wasi_nn::wit::WasiNnCtx; pub const DEFAULT_EPOCH_TICK_INTERVAL: u64 = 10; const PREVIEW1_ADAPTER: &[u8] = include_bytes!("adapters/wasi_snapshot_preview1.reactor.wasm"); +/// Outcome of one app call. +/// +/// Reported as the `outcome` label of `fastedge_calls_total` (see the server's +/// metrics sink) and, **as its discriminant**, as `fail_reason Int32` in the +/// ClickHouse stats table. Variants are therefore append-only: never reorder +/// or remove one. #[allow(non_camel_case_types)] #[derive(PartialEq, Copy, Clone, Debug)] pub enum AppResult { SUCCESS, - #[cfg(feature = "metrics")] UNKNOWN, TIMEOUT, OOM, OTHER, /// Request shed by admission control (server overloaded). - #[cfg(feature = "metrics")] OVERLOADED, /// Request rejected because the app is rate limited. - #[cfg(feature = "metrics")] RATE_LIMITED, /// Request rejected because the app is disabled or in draft. - #[cfg(feature = "metrics")] DISABLED, + /// Request rejected because the app is suspended. + SUSPENDED, } pub type InstancePre = wasmtime::component::InstancePre>; @@ -90,14 +104,48 @@ pub enum WasiVersion { /// Wrapper for the Preview 1 and Preview 2 versions of `WasiCtx`. pub enum Wasi { /// Preview 1 `WasiCtx` - Preview1(wasmtime_wasi::preview1::WasiP1Ctx), + Preview1(wasmtime_wasi::p1::WasiP1Ctx), /// Preview 2 `WasiCtx` Preview2(wasmtime_wasi::WasiCtx), } +/// The embedder-side `wasi:http` overrides, bundled with the guest state `T`. +/// +/// wasmtime 48 split the old `WasiHttpView` in two: the view now only hands out +/// the HTTP context, the resource table and a `&mut dyn WasiHttpHooks`, while +/// the overrides themselves (`send_request`, `is_forbidden_header`) moved onto +/// [`WasiHttpHooks`]. Because the hooks are borrowed out of the store data as a +/// single trait object, everything an override needs has to live in one struct. +/// That is this struct: it owns the guest state `T` (reached from the rest of +/// the runtime through `AsRef`/`AsMut` on [`Data`]) alongside the epoch +/// bookkeeping that `send_request` updates. +pub struct HttpHooks { + inner: T, + /// Milliseconds of host I/O that should not count against the epoch + /// deadline. Shared with the `epoch_deadline_callback` installed on the + /// Store, which drains this counter to extend the deadline. + epoch_pause_ms: Arc, + /// Whether elapsed time of external HTTP should refund epoch ticks. + pause_epoch_timeout_for_external_http: bool, +} + +impl HttpHooks { + pub fn new( + inner: T, + epoch_pause_ms: Arc, + pause_epoch_timeout_for_external_http: bool, + ) -> Self { + Self { + inner, + epoch_pause_ms, + pause_epoch_timeout_for_external_http, + } + } +} + /// Host state data associated with individual [Store]s and [Instance]s. pub struct Data { - inner: T, + hooks: HttpHooks, wasi: Wasi, pub wasi_nn: WasiNnCtx, // memory usage limiter @@ -111,13 +159,6 @@ pub struct Data { pub dictionary: Dictionary, pub utils: Utils, pub cache: cache::CacheImpl, - /// Milliseconds of host I/O that should not count against the epoch - /// deadline. The `epoch_deadline_callback` installed on the Store reads - /// and clears this counter, converting milliseconds into extra ticks to - /// extend the deadline. - pub epoch_pause_ms: Arc, - /// Whether elapsed time of external HTTP should refund epoch ticks. - pub pause_epoch_timeout_for_external_http: bool, /// Counts this instance in `fastedge_wasm_instances_live` for as long as the store — /// and therefore its pooling-allocator slots — is alive. Held only for its `Drop`. _live_instance: crate::instances::LiveInstanceGuard, @@ -125,17 +166,24 @@ pub struct Data { pub trait BackendRequest { fn backend_request(&mut self, head: Parts) -> anyhow::Result; + + /// Snapshot the outbound-request access-log handle, if configured. Default + /// `None` (no outbound-request logging). Implemented by embedders that carry + /// a sink; used by the WASI-HTTP send path. + fn ext_request_log_handle(&self) -> Option { + None + } } impl AsRef for Data { fn as_ref(&self) -> &T { - &self.inner + &self.hooks.inner } } impl AsMut for Data { fn as_mut(&mut self) -> &mut T { - &mut self.inner + &mut self.hooks.inner } } @@ -145,49 +193,171 @@ impl IoView for Data { } } -impl WasiHttpView for Data { - fn ctx(&mut self) -> &mut WasiHttpCtx { - &mut self.http +/// Boxed future used by [`WasiHttpHooks::send_request`] to report an error +/// raised while the request or response body was being processed. +type HttpIoFuture = Box> + Send>; + +/// Deposits host I/O wait time into the shared epoch-pause counter, carrying +/// sub-millisecond remainders across deposits so that bodies streamed in many +/// small frames do not have their wait time truncated away frame by frame. +struct EpochRefund { + epoch_pause_ms: Arc, + carry: Duration, +} + +impl EpochRefund { + fn new(epoch_pause_ms: Arc) -> Self { + Self { + epoch_pause_ms, + carry: Duration::ZERO, + } } - fn send_request( - &mut self, - request: Request, - config: OutgoingRequestConfig, - ) -> HttpResult - where - Self: Sized, - { - let (head, body) = request.into_parts(); - let head = self.inner.backend_request(head).map_err(|e| { - tracing::warn!(cause=?e, "backend request"); - ErrorCode::InternalError(Some(e.to_string())) - })?; - let use_tls = matches!(head.uri.scheme_str(), Some("https")); - let request = Request::from_parts(head, body); - // start external request stats timer - let stats = self.inner.get_stats(); - let epoch_pause_ms = self.epoch_pause_ms.clone(); - let epoch_exclude_http_wait = self.pause_epoch_timeout_for_external_http; + fn deposit(&mut self, elapsed: Duration) { + let total = self.carry + elapsed; + let ms = total.as_millis() as u64; + self.carry = total - Duration::from_millis(ms); + if ms > 0 { + self.epoch_pause_ms.fetch_add(ms, Ordering::Relaxed); + } + } +} - let handle = wasmtime_wasi::runtime::spawn(async move { - let _stats_timer = ExtStatsTimer::new(stats); // keep timer alive until request is done - let started = Instant::now(); - let resp = - default_send_request_handler(request, OutgoingRequestConfig { use_tls, ..config }) - .await; - if epoch_exclude_http_wait { - epoch_pause_ms.fetch_add(started.elapsed().as_millis() as u64, Ordering::Relaxed); +/// Response-body wrapper that refunds epoch ticks for time spent waiting on +/// the network. `default_send_request` resolves as soon as the response +/// *headers* are in; the body then streams lazily through the returned +/// `Response` and is only polled when the guest reads it. Any time a frame +/// poll stays `Pending` the guest is blocked on host I/O, so that time is +/// deposited into `epoch_pause_ms`, mirroring the send-phase refund in +/// [`WasiHttpHooks::send_request`]. +struct ResponseBodyEpochRefund { + inner: WasiBody, + refund: EpochRefund, + /// When the in-progress frame poll first returned `Pending`. + pending_since: Option, +} + +impl Body for ResponseBodyEpochRefund { + type Data = Bytes; + type Error = wasmtime_wasi_http::Error; + + fn poll_frame( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + let this = self.get_mut(); + let result = Pin::new(&mut this.inner).poll_frame(cx); + match &result { + Poll::Pending => { + this.pending_since.get_or_insert_with(Instant::now); } - Ok(resp) - }); - Ok(HostFutureIncomingResponse::pending(handle)) + Poll::Ready(_) => { + if let Some(started) = this.pending_since.take() { + this.refund.deposit(started.elapsed()); + } + } + } + result } - fn table(&mut self) -> &mut ResourceTable { - &mut self.table + fn is_end_stream(&self) -> bool { + self.inner.is_end_stream() + } + + fn size_hint(&self) -> SizeHint { + self.inner.size_hint() } +} + +impl Drop for ResponseBodyEpochRefund { + fn drop(&mut self) { + // Body dropped mid-wait (e.g. the request ended early): flush the tail. + if let Some(started) = self.pending_since.take() { + self.refund.deposit(started.elapsed()); + } + } +} + +/// Request-body wrapper covering the tail of the request-body lifetime. +/// +/// Until the response headers arrive, [`WasiHttpHooks::send_request`] refunds +/// the whole wall clock of `default_send_request`, which already includes +/// request-body streaming, so no extra accounting is needed there (and any +/// would double count). But hyper may still be sending the request body +/// *after* the headers arrived — driven by the background `io` future — while +/// the guest blocks in a body write waiting for backpressure to clear. That +/// wait shows up as the gap between this body yielding a frame and hyper +/// polling for the next one; a `Pending` poll, by contrast, means the guest +/// itself has not produced data yet and must stay on the clock. Gaps are +/// refunded only for their portion past `headers_at`. +struct RequestBodyEpochRefund { + inner: WasiBody, + refund: EpochRefund, + /// Set once the response headers are in (send-phase refund window closed). + headers_at: Arc>, + /// When the previous poll yielded a frame. + ready_since: Option, +} + +impl RequestBodyEpochRefund { + /// Refund the elapsed part of the current inter-poll gap that falls + /// outside the send-phase refund window. + fn settle_gap(&mut self) { + let Some(ready) = self.ready_since.take() else { + return; + }; + let Some(headers) = self.headers_at.get() else { + return; + }; + self.refund.deposit(ready.max(*headers).elapsed()); + } +} + +impl Body for RequestBodyEpochRefund { + type Data = Bytes; + type Error = wasmtime_wasi_http::Error; + + fn poll_frame( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + let this = self.get_mut(); + this.settle_gap(); + let result = Pin::new(&mut this.inner).poll_frame(cx); + if let Poll::Ready(Some(Ok(_))) = &result { + this.ready_since = Some(Instant::now()); + } + result + } + + fn is_end_stream(&self) -> bool { + self.inner.is_end_stream() + } + + fn size_hint(&self) -> SizeHint { + self.inner.size_hint() + } +} + +impl Drop for RequestBodyEpochRefund { + fn drop(&mut self) { + // hyper drops the body when it is done sending it (or the connection + // failed); settle the wait between the last yielded frame and now. + self.settle_gap(); + } +} + +impl WasiHttpView for Data { + fn http(&mut self) -> WasiHttpCtxView<'_> { + WasiHttpCtxView { + ctx: &mut self.http, + table: &mut self.table, + hooks: &mut self.hooks, + } + } +} +impl WasiHttpHooks for HttpHooks { fn is_forbidden_header(&mut self, name: &HeaderName) -> bool { // We want to allow the host header to be set. if name.eq(&header::HOST) { @@ -200,12 +370,104 @@ impl WasiHttpView for Data { return true; } // Fall back to wasmtime's default forbidden-header policy. - wasmtime_wasi_http::types::DEFAULT_FORBIDDEN_HEADERS.contains(name) + DEFAULT_FORBIDDEN_HEADERS.contains(name) + } + + fn send_request( + &mut self, + request: Request, + options: Option, + fut: HttpIoFuture, + ) -> Box< + dyn Future, HttpIoFuture)>> + Send, + > { + // The request-side error channel is unused: a request rejected by + // `backend_request` fails the whole call below instead. + let _ = fut; + + // Capture the outbound method/target and access-log handle before the + // request is consumed. The URI here is the original external target, + // before `backend_request` rewrites it to the internal backend + // authority. + let log_method = SmolStr::new(request.method().as_str()); + let log_uri = SmolStr::new(request.uri().to_string()); + let log_handle = self.inner.ext_request_log_handle(); + + let (head, body) = request.into_parts(); + let head = match self.inner.backend_request(head) { + Ok(head) => head, + Err(e) => { + tracing::warn!(cause=?e, "backend request"); + let cause = e.to_string(); + return Box::new(async move { + Err(wasmtime_wasi_http::Error::InternalError(Some(cause))) + }); + } + }; + // `default_send_request` derives TLS usage from the rewritten URI's + // scheme, so the backend rewrite above decides it. + let request = Request::from_parts(head, body); + // start external request stats timer + let stats = self.inner.get_stats(); + let epoch_pause_ms = self.epoch_pause_ms.clone(); + let epoch_exclude_http_wait = self.pause_epoch_timeout_for_external_http; + + Box::new(async move { + let _stats_timer = ExtStatsTimer::new(stats); // keep timer alive until response head is in + // Set once the response headers are in; the request-body wrapper + // only refunds waits that fall outside the send-phase window below. + let headers_at = Arc::new(OnceLock::new()); + let request = if epoch_exclude_http_wait { + request.map(|body| { + RequestBodyEpochRefund { + inner: body, + refund: EpochRefund::new(epoch_pause_ms.clone()), + headers_at: headers_at.clone(), + ready_since: None, + } + .boxed_unsync() + }) + } else { + request + }; + let started = Instant::now(); + let sent = default_send_request(request, options).await; + let elapsed = started.elapsed(); + if epoch_exclude_http_wait { + epoch_pause_ms.fetch_add(elapsed.as_millis() as u64, Ordering::Relaxed); + let _ = headers_at.set(Instant::now()); + } + // One access-log record per outbound request (status 0 = no response + // received, e.g. connection refused / timeout). + if let Some(handle) = log_handle { + let status = match &sent { + Ok((incoming, _)) => incoming.status().as_u16(), + Err(_) => 0, + }; + handle.log(&log_method, &log_uri, status, elapsed); + } + let (response, io) = sent?; + // The send-phase refund above stops at the response headers; the + // body wrappers keep refunding network wait past that point. + let response = if epoch_exclude_http_wait { + response.map(|body| { + ResponseBodyEpochRefund { + inner: body.boxed_unsync(), + refund: EpochRefund::new(epoch_pause_ms.clone()), + pending_since: None, + } + .boxed_unsync() + }) + } else { + response.map(BodyExt::boxed_unsync) + }; + Ok((response, Box::new(io) as HttpIoFuture)) + }) } } impl Data { - pub fn preview1_wasi_ctx_mut(&mut self) -> &mut wasmtime_wasi::preview1::WasiP1Ctx { + pub fn preview1_wasi_ctx_mut(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx { match &mut self.wasi { Wasi::Preview1(ctx) => ctx, Wasi::Preview2(_) => unreachable!("using WASI Preview 2 functions with Preview 1 ctx"), @@ -266,10 +528,8 @@ impl Default for WasmConfig { // Debug build: keep full, symbolized guest backtraces. We are optimizing // execution CPU, not trap-path cost, and this is the standalone debug // runner — detailed backtraces are the whole point. - inner.wasm_backtrace(true); inner.wasm_backtrace_details(WasmBacktraceDetails::Enable); - inner.async_support(true); inner.consume_fuel(false); // this is custom Gcore setting inner.profiler(ProfilingStrategy::None); inner.epoch_interruption(true); // required by store.rs timeout mechanism @@ -437,6 +697,25 @@ pub trait ContextT { caller_ip: Ipv4Addr, cfg: &App, ) -> Arc; + + /// Exact `User-Agent` value whose requests are not persisted to the stats + /// store (synthetic probes such as a security scanner). The comparison is + /// byte-exact and case-sensitive; `None` disables the filter. + fn stats_excluded_user_agent(&self) -> Option<&str> { + None + } + + /// Sink for the per-request system log (see [`access_log`]). + /// + /// Distinct from the guest application log produced by [`make_logger`]. + /// Defaults to a no-op until a concrete appender (e.g. UDP syslog) is wired + /// in, so existing embedders need not implement it. + /// + /// [`make_logger`]: ContextT::make_logger + fn access_log_sender(&self) -> &dyn AccessLogSender { + static NOOP: NoopAccessLogSender = NoopAccessLogSender; + &NOOP + } } pub trait ExecutorCache { diff --git a/crates/runtime/src/limiter.rs b/crates/runtime/src/limiter.rs index 355e7d1..c91d561 100644 --- a/crates/runtime/src/limiter.rs +++ b/crates/runtime/src/limiter.rs @@ -1,6 +1,5 @@ -use anyhow::{Error, Result}; use tracing::instrument; -use wasmtime::{ResourceLimiter, StoreLimits}; +use wasmtime::{Error, ResourceLimiter, Result, StoreLimits}; /// A proxy wrapper of `wasmtime::ResourceLimiter` trait impl used to track used memory #[derive(Clone, Debug)] diff --git a/crates/runtime/src/logger.rs b/crates/runtime/src/logger.rs index 9d1debd..b58a095 100644 --- a/crates/runtime/src/logger.rs +++ b/crates/runtime/src/logger.rs @@ -1,15 +1,11 @@ use async_trait::async_trait; use bytes::Bytes; use chrono::{DateTime, Utc}; -use std::any::Any; use std::collections::HashMap; -use std::io::IoSlice; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use tokio::io::{AsyncWrite, AsyncWriteExt}; -use wasi_common::file::{FdFlags, FileType}; -use wasi_common::{Error, WasiFile}; use wasmtime_wasi::cli::{IsTerminal, StdoutStream}; use wasmtime_wasi_io::poll::Pollable; use wasmtime_wasi_io::streams::OutputStream; @@ -273,27 +269,3 @@ impl OutputStream for Console { Ok(usize::MAX) } } - -#[async_trait] -impl WasiFile for Console { - fn as_any(&self) -> &dyn Any { - self - } - - async fn get_filetype(&self) -> Result { - Ok(FileType::Pipe) - } - - async fn get_fdflags(&self) -> Result { - Ok(FdFlags::APPEND) - } - - async fn write_vectored<'a>(&self, bufs: &[IoSlice<'a>]) -> Result { - let n = bufs.iter().fold(0, |n, buf| { - print!("{}", std::str::from_utf8(&buf).unwrap()); - n + buf.len() - }); - - Ok(n as u64) - } -} diff --git a/crates/runtime/src/registry.rs b/crates/runtime/src/registry.rs index cb502fa..38c9c81 100644 --- a/crates/runtime/src/registry.rs +++ b/crates/runtime/src/registry.rs @@ -39,7 +39,7 @@ impl GraphRegistry for StoreRegistry { impl CachedGraphRegistry { pub fn new() -> Self { let builder = Cache::builder() - // Max 10,000 entries + // Max 100 entries .max_capacity(100) // Time to idle (TTI): 30 minutes .time_to_idle(Duration::from_secs(30 * 60)); diff --git a/crates/runtime/src/store.rs b/crates/runtime/src/store.rs index e36f915..162ae55 100644 --- a/crates/runtime/src/store.rs +++ b/crates/runtime/src/store.rs @@ -2,7 +2,7 @@ use crate::limiter::ProxyLimiter; use crate::logger::Logger; use crate::registry::CachedGraphRegistry; use crate::util::stats::StatsVisitor; -use crate::{DEFAULT_EPOCH_TICK_INTERVAL, Data, Wasi, WasiVersion}; +use crate::{DEFAULT_EPOCH_TICK_INTERVAL, Data, HttpHooks, Wasi, WasiVersion}; use anyhow::Result; use secret::SecretStore; use std::sync::Arc; @@ -353,7 +353,7 @@ impl StoreBuilder { let mut inner = wasmtime::Store::new( &self.engine, Data { - inner, + hooks: HttpHooks::new(inner, epoch_pause_ms.clone(), self.epoch_exclude_http_wait), wasi, wasi_nn, store_limits: self.store_limits, @@ -374,8 +374,6 @@ impl StoreBuilder { dictionary: self.dictionary, utils, cache: cache_impl, - epoch_pause_ms: epoch_pause_ms.clone(), - pause_epoch_timeout_for_external_http: self.epoch_exclude_http_wait, _live_instance: crate::instances::LiveInstanceGuard::new(), }, ); @@ -387,7 +385,7 @@ impl StoreBuilder { inner.epoch_deadline_callback(move |_ctx| { let credit_ms = epoch_pause_ms.swap(0, Ordering::Relaxed); match epoch_credit_ticks(credit_ms) { - None => Err(anyhow::Error::new(wasmtime::Trap::Interrupt)), + None => Err(wasmtime::Error::new(wasmtime::Trap::Interrupt)), Some(ticks) => Ok(UpdateDeadline::Continue(ticks)), } }); @@ -498,7 +496,7 @@ mod tests { store.epoch_deadline_callback(move |_ctx| { let credit_ms = epoch_pause_ms.swap(0, Ordering::Relaxed); match epoch_credit_ticks(credit_ms) { - None => Err(anyhow::Error::new(Trap::Interrupt)), + None => Err(wasmtime::Error::new(Trap::Interrupt)), Some(ticks) => Ok(UpdateDeadline::Continue(ticks)), } }); @@ -602,10 +600,8 @@ mod tests { /// No-op stats sink; `StoreBuilder::build` only needs `HasStats` to wire the /// key-value store and utils host state. - #[cfg(feature = "metrics")] struct NoStats; - #[cfg(feature = "metrics")] mod no_stats_impls { use super::NoStats; use crate::util::stats::{CdnPhase, ReadStats, StatsVisitor}; @@ -639,7 +635,6 @@ mod tests { } } - #[cfg(feature = "metrics")] impl HasStats for NoStats { fn get_stats(&self) -> Arc { Arc::new(NoStats) @@ -649,7 +644,6 @@ mod tests { /// End-to-end wiring check: a store built the way every executor builds it must be /// counted in `fastedge_wasm_instances_live` for exactly as long as it is alive, since /// that is the window in which it holds pooling-allocator slots. - #[cfg(feature = "metrics")] #[test] fn store_lifetime_is_counted_as_a_live_instance() { use crate::instances; diff --git a/crates/runtime/src/trace.rs b/crates/runtime/src/trace.rs new file mode 100644 index 0000000..cbdbd27 --- /dev/null +++ b/crates/runtime/src/trace.rs @@ -0,0 +1,60 @@ +//! W3C trace-context helpers. + +use rand::RngCore; +use smol_str::SmolStr; +use std::fmt::Write; + +/// Generate a fresh W3C `traceparent`: `00---01`, with a +/// random 16-byte trace-id and 8-byte parent-id and the sampled flag set. +/// +/// Used as the fallback when an inbound request does not carry a `traceparent` +/// header, so every record on the trace stream has a valid, joinable id. +pub fn new_traceparent() -> SmolStr { + let mut rng = rand::thread_rng(); + let mut trace_id = [0u8; 16]; + let mut span_id = [0u8; 8]; + rng.fill_bytes(&mut trace_id); + rng.fill_bytes(&mut span_id); + // The all-zero value is invalid per the spec; nudge it (astronomically + // unlikely, but effectively free to guard). + if trace_id.iter().all(|&b| b == 0) { + trace_id[0] = 1; + } + if span_id.iter().all(|&b| b == 0) { + span_id[0] = 1; + } + + // "00-" + 32 + "-" + 16 + "-01" = 55 chars. + let mut s = String::with_capacity(55); + s.push_str("00-"); + for b in trace_id { + let _ = write!(s, "{:02x}", b); + } + s.push('-'); + for b in span_id { + let _ = write!(s, "{:02x}", b); + } + s.push_str("-01"); + SmolStr::new(s) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn well_formed_and_unique() { + let tp = new_traceparent(); + let parts: Vec<&str> = tp.split('-').collect(); + assert_eq!(parts.len(), 4, "four dash-separated fields: {tp}"); + assert_eq!(parts[0], "00"); + assert_eq!(parts[1].len(), 32); + assert_eq!(parts[2].len(), 16); + assert_eq!(parts[3], "01"); + assert!(parts[1].bytes().all(|c| c.is_ascii_hexdigit())); + assert!(parts[2].bytes().all(|c| c.is_ascii_hexdigit())); + assert_ne!(parts[1], "0".repeat(32)); + assert_ne!(parts[2], "0".repeat(16)); + assert_ne!(new_traceparent(), new_traceparent()); + } +} diff --git a/crates/runtime/src/util/metrics.rs b/crates/runtime/src/util/metrics.rs index 7d5b31a..90baff4 100644 --- a/crates/runtime/src/util/metrics.rs +++ b/crates/runtime/src/util/metrics.rs @@ -1,70 +1,61 @@ -use lazy_static::lazy_static; -use prometheus::{ - self, HistogramVec, IntCounterVec, register_histogram_vec, register_int_counter_vec, -}; +//! App-call metrics hook. +//! +//! The runtime reports every app-call outcome through a process-global sink so +//! that embedders (e.g. the FastEdge server) can export them however they like +//! (Prometheus, etc.) without this crate depending on a metrics backend. When +//! no sink is installed (e.g. the `fastedge-run` CLI) reporting is a no-op. + +use std::sync::OnceLock; use crate::AppResult; -lazy_static! { - static ref TOTAL_COUNT: IntCounterVec = register_int_counter_vec!( - "fastedge_call_count", - "Total number of app calls.", - &["executor"] - ) - .unwrap(); - static ref ERROR_COUNT: IntCounterVec = register_int_counter_vec!( - "fastedge_error_total_count", - "Total number of failed app calls, by executor and failure reason \ - (counter; the non-conventional *_count suffix is kept for backwards compatibility).", - &["executor", "reason"] - ) - .unwrap(); - static ref REQUEST_DURATION: HistogramVec = register_histogram_vec!( - "fastedge_request_duration", - "Request duration", - &["executor"], - vec![ - 0.0001, 0.0002, 0.0003, 0.0005, 0.001, 0.002, 0.003, 0.005, 0.01, 0.02, 0.03, 0.05, - 0.1, 0.2, 0.3, 0.5, 1.0, 10.0 - ] - ) - .unwrap(); - static ref MEMORY_USAGE: IntCounterVec = register_int_counter_vec!( - "fastedge_wasm_memory_used", - "Cumulative WASM linear-memory bytes consumed by app calls (counter, NOT a \ - current-usage gauge; the name is kept for backwards compatibility). \ - rate() = memory-zeroing throughput; divide by rate(fastedge_call_count) \ - for average memory per request.", - &["executor"] - ) - .unwrap(); +/// Where in an app call the outcome was decided. +/// +/// Refines the metrics `outcome` label only — stats (`fail_reason`) always +/// carry the plain [`AppResult`], so the ClickHouse encoding is untouched. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CallPhase { + /// Guest initialisation (`_initialize`, `on_context_create`): no request + /// callback had run yet. + Init, + /// A request callback (`on_request_headers`, body chunks, `on_log`, …), or + /// a rejection before the guest was involved at all. + Request, } -pub fn metrics(result: AppResult, label: &[&str], duration: Option, memory_used: Option) { - TOTAL_COUNT.with_label_values(label).inc(); +/// Sink invoked for every app call: outcome, the phase it was decided in, +/// executor label(s), duration in microseconds and WASM linear memory used in +/// bytes. +pub type MetricsSink = fn( + result: AppResult, + phase: CallPhase, + label: &[&str], + duration: Option, + memory_used: Option, +); - if result != AppResult::SUCCESS { - let mut values: Vec<&str> = label.iter().map(|v| *v).collect(); - match result { - AppResult::UNKNOWN => values.push("unknown"), - AppResult::TIMEOUT => values.push("timeout"), - AppResult::OOM => values.push("oom"), - AppResult::OTHER => values.push("other"), - AppResult::OVERLOADED => values.push("overloaded"), - AppResult::RATE_LIMITED => values.push("ratelimited"), - AppResult::DISABLED => values.push("disabled"), - _ => {} - }; +static SINK: OnceLock = OnceLock::new(); - ERROR_COUNT.with_label_values(values.as_slice()).inc(); - } +/// Install the process-global metrics sink. Subsequent calls are ignored. +pub fn set_sink(sink: MetricsSink) { + let _ = SINK.set(sink); +} - if let Some(duration) = duration { - REQUEST_DURATION - .with_label_values(label) - .observe((duration as f64) / 1_000_000.0); - } - if let Some(memory_used) = memory_used { - MEMORY_USAGE.with_label_values(label).inc_by(memory_used); +/// Report one app call decided in the request phase (no-op when no sink is +/// installed). The common case; see [`metrics_in_phase`] for the rest. +pub fn metrics(result: AppResult, label: &[&str], duration: Option, memory_used: Option) { + metrics_in_phase(result, CallPhase::Request, label, duration, memory_used); +} + +/// Report one app call, stating the phase its outcome was decided in. +pub fn metrics_in_phase( + result: AppResult, + phase: CallPhase, + label: &[&str], + duration: Option, + memory_used: Option, +) { + if let Some(sink) = SINK.get() { + sink(result, phase, label, duration, memory_used); } } diff --git a/crates/runtime/src/util/mod.rs b/crates/runtime/src/util/mod.rs index 41c2dc3..d14ad41 100644 --- a/crates/runtime/src/util/mod.rs +++ b/crates/runtime/src/util/mod.rs @@ -1,3 +1,2 @@ -#[cfg(feature = "metrics")] pub mod metrics; pub mod stats; diff --git a/crates/runtime/src/util/stats.rs b/crates/runtime/src/util/stats.rs index ca5abd2..2cb5712 100644 --- a/crates/runtime/src/util/stats.rs +++ b/crates/runtime/src/util/stats.rs @@ -44,6 +44,10 @@ pub trait StatsVisitor: ReadStats + UserDiagStats + ExtRequestStats + Send + Syn fn get_memory_used(&self) -> u64; /// Register cdn phase fn cdn_phase(&self, phase: CdnPhase); + /// Mark the record as not to be persisted to the stats store (e.g. a + /// synthetic probe that must not count as traffic). Counters keep being + /// collected and Prometheus metrics / logs are unaffected. No-op by default. + fn discard(&self) {} } impl StatsTimer { diff --git a/src/context.rs b/src/context.rs index ab93365..1bc6481 100644 --- a/src/context.rs +++ b/src/context.rs @@ -42,11 +42,11 @@ pub struct Context { impl PreCompiledLoader for Context { fn load_component(&self, _id: u64) -> anyhow::Result { let wasm_sample = componentize_if_necessary(&self.wasm_bytes)?; - Component::new(&self.engine, wasm_sample) + Ok(Component::new(&self.engine, wasm_sample)?) } fn load_module(&self, _id: u64) -> anyhow::Result { - unreachable!("") + unreachable!("fastedge-run loads components only, not core wasm modules") } }