From 0bfeaf49053fafa7b45397534ebc9db34fe4a7e4 Mon Sep 17 00:00:00 2001 From: Sock <87631534+platonicsock@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:26:50 -0400 Subject: [PATCH] [anneal] Add playground Rust server gherrit-pr-id: Gzpgd7p6so5xn3tfstq45vnmycc7weotp --- .../rust-anneal-playground/ui/.gitignore | 3 + .../rust-anneal-playground/ui/Cargo.lock | 3119 +++++++++++++++++ .../rust-anneal-playground/ui/Cargo.toml | 34 + .../rust-anneal-playground/ui/README.md | 45 + .../rust-anneal-playground/ui/plan.txt | 125 + .../rust-anneal-playground/ui/src/env.rs | 5 + .../rust-anneal-playground/ui/src/gist.rs | 54 + .../rust-anneal-playground/ui/src/main.rs | 300 ++ .../rust-anneal-playground/ui/src/metrics.rs | 357 ++ .../ui/src/public_http_api.rs | 207 ++ .../ui/src/request_database.rs | 261 ++ .../ui/src/server_axum.rs | 1547 ++++++++ .../ui/src/server_axum/cache.rs | 254 ++ .../ui/src/server_axum/websocket.rs | 951 +++++ 14 files changed, 7262 insertions(+) create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/.gitignore create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/Cargo.lock create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/Cargo.toml create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/README.md create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/plan.txt create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/src/env.rs create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/src/gist.rs create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/src/main.rs create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/src/metrics.rs create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/src/public_http_api.rs create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/src/request_database.rs create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/src/server_axum.rs create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/src/server_axum/cache.rs create mode 100644 anneal/v1/playground/rust-anneal-playground/ui/src/server_axum/websocket.rs diff --git a/anneal/v1/playground/rust-anneal-playground/ui/.gitignore b/anneal/v1/playground/rust-anneal-playground/ui/.gitignore new file mode 100644 index 0000000000..29b8ac960f --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/.gitignore @@ -0,0 +1,3 @@ +.env +access-log.csv +target diff --git a/anneal/v1/playground/rust-anneal-playground/ui/Cargo.lock b/anneal/v1/playground/rust-anneal-playground/ui/Cargo.lock new file mode 100644 index 0000000000..cb1c1db8b6 --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/Cargo.lock @@ -0,0 +1,3119 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + +[[package]] +name = "asm-cleanup" +version = "0.1.0" +dependencies = [ + "petgraph", + "regex", + "rustc-demangle", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "base64", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-extra" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970" +dependencies = [ + "axum", + "axum-core", + "bytes", + "futures-core", + "futures-util", + "headers", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +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 = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "10.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" +dependencies = [ + "base64", + "ed25519-dalek", + "getrandom 0.2.17", + "hmac", + "js-sys", + "p256", + "p384", + "pem", + "rand 0.8.6", + "rsa", + "serde", + "serde_json", + "sha2", + "signature", + "simple_asn1", + "zeroize", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libsqlite3-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76001fb4daed01e5f2b518aac0b4dc592e7c734da63dbffcf0c64fa612a8d0c" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" + +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "modify-cargo-toml" +version = "0.1.0" +dependencies = [ + "serde", + "serde_derive", + "toml", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.6", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "octocrab" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2ad8abffe4e2b05f9cdc7e061de63d305a6dca0af81ca1064a7d98e0b78267" +dependencies = [ + "arc-swap", + "async-trait", + "base64", + "bytes", + "cargo_metadata", + "cfg-if", + "chrono", + "futures", + "futures-util", + "getrandom 0.2.17", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "jsonwebtoken", + "once_cell", + "percent-encoding", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "snafu 0.8.9", + "tokio", + "tower", + "tower-http", + "tracing", + "url", + "web-time", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "orchestrator" +version = "0.1.0" +dependencies = [ + "asm-cleanup", + "bincode", + "futures", + "libc", + "mach2", + "modify-cargo-toml", + "procfs", + "serde", + "serde_json", + "snafu 0.9.1", + "strum_macros", + "tokio", + "tokio-stream", + "tokio-util", + "toml", + "tracing", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "procfs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" +dependencies = [ + "bitflags", + "procfs-core", + "rustix", +] + +[[package]] +name = "procfs-core" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" +dependencies = [ + "bitflags", + "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", + "memchr", + "parking_lot", + "thiserror", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rusqlite" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b3492ea85308705c3a5cc24fb9b9cf77273d30590349070db42991202b214c4" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive 0.8.9", +] + +[[package]] +name = "snafu" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1a012328be2e3f5d5f6f3218147ca02588cea4cb865e876849ab6debcf36522" +dependencies = [ + "futures-core", + "pin-project", + "snafu-derive 0.9.1", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "snafu-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f103c50866b8743da9429b8a581d81a27c2d3a9c4ac7df8f8571c1dd7896eda" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +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", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.4", + "sha1", + "thiserror", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ui" +version = "0.1.0" +dependencies = [ + "asm-cleanup", + "axum", + "axum-extra", + "dotenv", + "futures", + "octocrab", + "orchestrator", + "prometheus", + "regex", + "rusqlite", + "serde", + "serde_derive", + "serde_json", + "snafu 0.9.1", + "strum", + "tempfile", + "tokio", + "tokio-util", + "tower-http", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/anneal/v1/playground/rust-anneal-playground/ui/Cargo.toml b/anneal/v1/playground/rust-anneal-playground/ui/Cargo.toml new file mode 100644 index 0000000000..0e28824047 --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/Cargo.toml @@ -0,0 +1,34 @@ +[package] +authors = ["Jake Goulding "] +name = "ui" +version = "0.1.0" +edition = "2018" + +[workspace] + +[features] +default = ['fork-bomb-prevention'] +fork-bomb-prevention = [] + +[dependencies] +asm-cleanup = { path = "../compiler/base/asm-cleanup" } +axum = { version = "0.8", features = ["ws"] } +axum-extra = { version = "0.12", features = ["typed-header"] } +dotenv = "0.15.0" +futures = "0.3.21" +octocrab = "0.51" +orchestrator = { path = "../compiler/base/orchestrator" } +prometheus = { version = "0.14.0", default-features = false } +regex = "1.0.0" +rusqlite = { version = "0.40.0", default-features = false, features = ["bundled"] } +serde = { version = "1.0", features = ["rc"] } +serde_derive = "1.0" +serde_json = "1.0" +snafu = "0.9.0" +strum = { version = "0.28.0", features = ["derive"] } +tempfile = "3" +tokio = { version = "1.9", features = ["macros", "time", "process", "rt-multi-thread"] } +tokio-util = { version = "0.7.9", default-features = false, features = ["time"] } +tower-http = { version = "0.6", features = ["cors", "fs", "request-id", "set-header", "trace"] } +tracing = { version = "0.1.37", features = ["attributes"] } +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } diff --git a/anneal/v1/playground/rust-anneal-playground/ui/README.md b/anneal/v1/playground/rust-anneal-playground/ui/README.md new file mode 100644 index 0000000000..98708199eb --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/README.md @@ -0,0 +1,45 @@ +## Configuration + +When developing, you can place these in a [`.env`][dotenv] file on +disk in this directory. + +In production, these should be set according to your deployment method +of choice. + +| Key | Required | Default Value | Description | +| -------------------------- | -------- | ----------------- | ------------------------------------------------------------------------------------- | +| `PLAYGROUND_UI_ROOT` | No | | The path to the HTML, CSS, and Javascript files (the directory containing index.html) | +| `PLAYGROUND_GITHUB_TOKEN` | No | | The [GitHub API token][gist] to read and write Gists | +| `PLAYGROUND_UI_ADDRESS` | No | 127.0.0.1 | The address to listen on | +| `PLAYGROUND_UI_PORT` | No | 5000 | The port to listen on | +| `PLAYGROUND_METRICS_TOKEN` | No | | If set, will require authentication for the metrics endpoint | +| `PLAYGROUND_CORS_ENABLED` | No | | If set, will enable CORS support | +| `TMPDIR` | No | system-provided | Where compilation artifacts will be saved. Must be accessible to Docker | + +[dotenv]: https://crates.io/crates/dotenv +[gist]: https://developer.github.com/v3/gists/#authentication + +### Troubleshooting + +#### macOS + +After launching `ui`, when you try to do any action (ex. `build`, `rustfmt`, `run` and so on), you get errors from Docker about "Mounts denied": + +``` +docker: Error response from daemon: Mounts denied: +The paths /var/folders/dx/l5pn75zx5v9_cwstvgwc5qyc0000gn/T/playground.6gEHdGUM6XPU/output and /var/folders/dx/l5pn75zx5v9_cwstvgwc5qyc0000gn/T/playground.6gEHdGUM6XPU/input.rs +are not shared from OS X and are not known to Docker. +You can configure shared paths from Docker -> Preferences... -> File Sharing. +See https://docs.docker.com/docker-for-mac/osxfs/#namespaces for more info. +. +time="2099-12-31T00:00:00+00:00" level=error msg="error waiting for container: context canceled" +``` + +To fix this issue, set the `TMPDIR` environment variable to a path that Docker can mount: + +``` +mkdir tmp +TMPDIR=$PWD/tmp cargo run +``` + +(Note: This was reported at [#480](https://github.com/rust-lang/rust-playground/issues/480)) diff --git a/anneal/v1/playground/rust-anneal-playground/ui/plan.txt b/anneal/v1/playground/rust-anneal-playground/ui/plan.txt new file mode 100644 index 0000000000..57d3a2a18f --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/plan.txt @@ -0,0 +1,125 @@ +# Anneal Playground Fork Plan + +## Summary +Build a production-ready fork of Rust Playground that adds **Verify with Anneal** as a Build menu action. Selecting it will use the existing execute pipeline, but send an additional execution-tool choice so the backend runs `cargo anneal verify` inside the sandbox container instead of normal `cargo run`. Deploy v1 on a single Docker-capable Google Compute Engine VM. + +## Item-By-Item Plan +1. **Fork and set up the repo** + - Fork `rust-lang/rust-playground`. + - Clone the fork locally and add upstream remote. + - Build frontend with `pnpm install` / `pnpm build`. + - Build backend with `cargo build` from `ui`. + - Fetch or build current compiler Docker images to confirm the unmodified fork works. + +2. **Add the frontend Anneal action** + - Add a new primary action enum value, e.g. `AnnealVerify`. + - Add **Verify with Anneal** to the Build menu beside Run/Build/Test. + - Make selecting it update the primary button label to **Verify with Anneal**. + - Extend execute payload creation to include: + ```ts + executionTool: "cargo" | "anneal-verify" + ``` + - Default all existing actions to `"cargo"`. + +3. **Carry the new field through HTTP and WebSocket execution** + - Update the frontend execute request body type. + - Update websocket execute payload type. + - Update backend `api::ExecuteRequest` to accept `executionTool`. + - Use a default of `"cargo"` if the field is missing so old requests still work. + - Update websocket backend request parsing with the same field and default. + +4. **Add backend validation and internal type** + - Add an orchestrator enum: + ```rust + ExecutionTool::Cargo + ExecutionTool::AnnealVerify + ``` + - Parse API strings into this enum during `api::ExecuteRequest -> coordinator::ExecuteRequest`. + - Reject unknown values with the same style as existing `parse_channel`, `parse_mode`, etc. + - Add the field to metrics labels only if useful; otherwise keep existing execute metrics unchanged for v1. + +5. **Change command selection** + - In the orchestrator execute command builder: + - If `executionTool == Cargo`, keep existing behavior: `cargo run`, `cargo test`, or `cargo build`. + - If `executionTool == AnnealVerify`, run: + ```text + cargo anneal verify + ``` + - Keep writing the user code and generated `Cargo.toml` through the existing Playground setup path. + +6. **Install Anneal inside compiler containers** + - Update the compiler Docker image to install a pinned `cargo-anneal` version. + - Run `cargo anneal setup` during image build so Lean/Aeneas/Charon dependencies are preinstalled. + - Confirm runtime containers still run with no network access. + - Increase container memory/time limits only if testing shows Anneal fails under current limits. + +7. **Local verification** + - Build `rust-stable`, `rust-beta`, and `rust-nightly` images or decide to support only stable initially. + - Run the backend locally. + - Verify existing Run/Build/Test behavior still works. + - Verify **Verify with Anneal** works on a small annotated example. + - Verify invalid `executionTool` values are rejected before reaching command execution. + +8. **Production deployment** + - Create a Google Compute Engine VM with Docker support. + - Recommended starter: 4 vCPU, 8 GB RAM, 80-160 GB SSD. + - Install Docker, firewall rules, system updates, and a reverse proxy such as Caddy for HTTPS. + - Deploy the frontend/backend and compiler images. + - Configure concurrency to start at one verification job at a time. + - Add restart behavior via systemd or equivalent. + +9. **Production hardening** + - Keep Docker runtime network disabled for verification containers. + - Keep memory/process limits enabled. + - Add disk cleanup for temporary build directories, old Docker layers, and logs. + - Add basic monitoring: uptime, disk usage, memory usage, failed verification count. + - Add backups/snapshots before major deploys. + - Add a simple abuse-control layer: request size limit, per-IP rate limit, and queue/concurrency cap. + +10. **Completion criteria** + - Public URL loads the forked Playground over HTTPS. + - Run/Build/Test still behave like upstream. + - Build menu includes **Verify with Anneal**. + - Selecting it runs `cargo anneal verify` inside the sandbox container. + - Output appears in the normal execution output pane. + - Existing malformed requests fail safely. + - Server survives restart and resumes serving the site. + - Basic monitoring and cleanup are in place. + +## Public Interfaces And Types +- Add request field: + ```ts + executionTool?: "cargo" | "anneal-verify" + ``` +- Backend default: + ```text + missing executionTool => cargo + ``` +- New user-facing action: + ```text + Verify with Anneal + ``` +- New backend internal enum: + ```rust + ExecutionTool::{Cargo, AnnealVerify} + ``` + +## Test Plan +- Frontend: payload selector sends `"anneal-verify"` only for the Anneal action. +- Frontend: Build menu action updates primary button label. +- Backend: HTTP `/execute` accepts missing `executionTool`. +- Backend: HTTP `/execute` rejects unknown `executionTool`. +- Backend: WebSocket execute path accepts the new field. +- Orchestrator: Cargo mode preserves existing `run/test/build` behavior. +- Orchestrator: Anneal mode produces `cargo anneal verify`. +- Docker: compiler image contains `cargo-anneal` and preinstalled Anneal toolchain. +- End-to-end: small Anneal example succeeds; intentionally failing proof returns useful stderr. +- Regression: normal Rust `fn main()` still runs successfully. + +## Assumptions +- v1 uses a **single Google Compute Engine VM**. +- v1 uses a **Build menu item** rather than a separate persistent dropdown. +- v1 is **production-ready enough for a small public demo**, not high-traffic SaaS. +- v1 starts with **one verification job at a time**. +- `cargo-anneal` version is pinned during Docker image build. +- If Anneal requires more than current Playground Docker limits, raise limits based on measured local failures. diff --git a/anneal/v1/playground/rust-anneal-playground/ui/src/env.rs b/anneal/v1/playground/rust-anneal-playground/ui/src/env.rs new file mode 100644 index 0000000000..9b8fb76fb2 --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/src/env.rs @@ -0,0 +1,5 @@ +pub use std::env::*; + +pub const PLAYGROUND_GITHUB_TOKEN: &str = "PLAYGROUND_GITHUB_TOKEN"; +pub const PLAYGROUND_ANNEAL_PREWARM_STABLE: &str = "PLAYGROUND_ANNEAL_PREWARM_STABLE"; +pub const PLAYGROUND_UI_ROOT: &str = "PLAYGROUND_UI_ROOT"; diff --git a/anneal/v1/playground/rust-anneal-playground/ui/src/gist.rs b/anneal/v1/playground/rust-anneal-playground/ui/src/gist.rs new file mode 100644 index 0000000000..b2bf9183c3 --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/src/gist.rs @@ -0,0 +1,54 @@ +use octocrab::Octocrab; + +const FILENAME: &str = "playground.rs"; +const DESCRIPTION: &str = "Code shared from the Rust Playground"; + +pub struct Gist { + pub id: String, + pub url: String, + pub code: String, +} + +impl From for Gist { + fn from(other: octocrab::models::gists::Gist) -> Self { + let mut files: Vec<_> = other + .files + .into_iter() + .map(|(name, file)| (name, file.content.unwrap_or_default())) + .collect(); + + files.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); + + let code = match files.len() { + 0 | 1 => files.into_iter().map(|(_, content)| content).collect(), + _ => files + .into_iter() + .map(|(name, content)| format!("// {name}\n{content}\n\n")) + .collect(), + }; + + Gist { id: other.id, url: other.html_url.into(), code } + } +} + +pub async fn create_future(token: String, code: String) -> octocrab::Result { + github(token)? + .gists() + .create() + .description(DESCRIPTION) + .public(false) + .file(FILENAME, code) + .send() + .await + .map(Into::into) +} + +pub async fn load_future(token: String, id: &str) -> octocrab::Result { + let github = github(token)?; + + github.gists().get(id).await.map(Into::into) +} + +fn github(token: String) -> octocrab::Result { + octocrab::OctocrabBuilder::new().personal_token(token).build() +} diff --git a/anneal/v1/playground/rust-anneal-playground/ui/src/main.rs b/anneal/v1/playground/rust-anneal-playground/ui/src/main.rs new file mode 100644 index 0000000000..5c8126b7ef --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/src/main.rs @@ -0,0 +1,300 @@ +#![deny(rust_2018_idioms)] + +use orchestrator::coordinator::{ + limits::{self, Acquisition}, + CoordinatorFactory, ResourceLimits, +}; +use std::{ + net::SocketAddr, + path::{Path, PathBuf}, + sync::Arc, + time::Duration, +}; +use tracing::{error, info, warn}; +use tracing_subscriber::EnvFilter; + +const DEFAULT_ADDRESS: &str = "127.0.0.1"; +const DEFAULT_PORT: u16 = 5000; + +const DEFAULT_WEBSOCKET_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5); +const DEFAULT_WEBSOCKET_IDLE_TIMEOUT: Duration = Duration::from_secs(60); +const DEFAULT_WEBSOCKET_IDLE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); +const DEFAULT_WEBSOCKET_SESSION_TIMEOUT: Duration = Duration::from_secs(45 * 60); +const DEFAULT_WEBSOCKET_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); + +const DEFAULT_COORDINATORS_LIMIT: usize = 25; +const DEFAULT_PROCESSES_LIMIT: usize = 10; + +mod env; +mod gist; +mod metrics; +mod public_http_api; +mod request_database; +mod server_axum; + +use env::{PLAYGROUND_ANNEAL_PREWARM_STABLE, PLAYGROUND_GITHUB_TOKEN, PLAYGROUND_UI_ROOT}; + +#[tokio::main] +async fn main() { + // Dotenv may be unable to load environment variables, but that's ok in production + let _ = dotenv::dotenv(); + + // Info-level logging is enabled by default. + tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); + + let config = Config::from_env(); + server_axum::serve(config).await; +} + +#[derive(Copy, Clone)] +pub(crate) struct FeatureFlags {} + +struct Config { + address: String, + cors_enabled: bool, + gh_token: Option, + metrics_token: Option, + feature_flags: FeatureFlags, + request_db_path: Option, + anneal_prewarm_stable: bool, + websocket_config: WebSocketConfig, + limits: Arc, + port: u16, + root: PathBuf, +} + +impl Config { + fn from_env() -> Self { + let root = if let Some(root) = env::var_os(PLAYGROUND_UI_ROOT) { + // Ensure it appears as an absolute path in logs to help user orient + // themselves about what directory the PLAYGROUND_UI_ROOT + // configuration is interpreted relative to. + let mut root = PathBuf::from(root); + if !root.is_absolute() { + if let Ok(current_dir) = env::current_dir() { + root = current_dir.join(root); + } + } + root + } else { + // Note this is `env!` (compile time) while the above is + // `env::var_os` (run time). We know where the ui is expected to be + // relative to the source code that the server was compiled from. + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("frontend").join("build") + }; + + let index_html = root.join("index.html"); + if index_html.exists() { + info!("Serving playground frontend from {}", root.display()); + } else { + error!( + "Playground ui does not exist at {}\n\ + Playground will not work until `pnpm build` has been run or {PLAYGROUND_UI_ROOT} has been fixed", + index_html.display(), + ); + } + + let address = + env::var("PLAYGROUND_UI_ADDRESS").unwrap_or_else(|_| DEFAULT_ADDRESS.to_string()); + let port = env::var("PLAYGROUND_UI_PORT") + .ok() + .and_then(|p| p.parse().ok()) + .unwrap_or(DEFAULT_PORT); + + let gh_token = env::var(PLAYGROUND_GITHUB_TOKEN).ok(); + if gh_token.is_none() { + warn!("Environment variable {} is not set, so reading and writing GitHub gists will not work", PLAYGROUND_GITHUB_TOKEN); + } + + let metrics_token = env::var("PLAYGROUND_METRICS_TOKEN").ok(); + + let cors_enabled = env::var_os("PLAYGROUND_CORS_ENABLED").is_some(); + + let feature_flags = FeatureFlags {}; + + let request_db_path = env::var_os("PLAYGROUND_REQUEST_DATABASE").map(Into::into); + let anneal_prewarm_stable = env::var_os(PLAYGROUND_ANNEAL_PREWARM_STABLE).is_some(); + + let websocket_config = { + let handshake_timeout = env::var("PLAYGROUND_WEBSOCKET_HANDSHAKE_TIMEOUT_S") + .ok() + .and_then(|l| l.parse().map(Duration::from_secs).ok()) + .unwrap_or(DEFAULT_WEBSOCKET_HANDSHAKE_TIMEOUT); + + let idle_timeout = env::var("PLAYGROUND_WEBSOCKET_IDLE_TIMEOUT_S") + .ok() + .and_then(|l| l.parse().map(Duration::from_secs).ok()) + .unwrap_or(DEFAULT_WEBSOCKET_IDLE_TIMEOUT); + + let idle_shutdown_timeout = env::var("PLAYGROUND_WEBSOCKET_IDLE_SHUTDOWN_TIMEOUT_S") + .ok() + .and_then(|l| l.parse().map(Duration::from_secs).ok()) + .unwrap_or(DEFAULT_WEBSOCKET_IDLE_SHUTDOWN_TIMEOUT); + + let session_timeout = env::var("PLAYGROUND_WEBSOCKET_SESSION_TIMEOUT_S") + .ok() + .and_then(|l| l.parse().map(Duration::from_secs).ok()) + .unwrap_or(DEFAULT_WEBSOCKET_SESSION_TIMEOUT); + + let shutdown_timeout = env::var("PLAYGROUND_WEBSOCKET_SHUTDOWN_TIMEOUT_S") + .ok() + .and_then(|l| l.parse().map(Duration::from_secs).ok()) + .unwrap_or(DEFAULT_WEBSOCKET_SHUTDOWN_TIMEOUT); + + WebSocketConfig { + handshake_timeout, + idle_timeout, + idle_shutdown_timeout, + session_timeout, + shutdown_timeout, + } + }; + + let coordinators_limit = env::var("PLAYGROUND_COORDINATORS_LIMIT") + .ok() + .and_then(|l| l.parse().ok()) + .unwrap_or(DEFAULT_COORDINATORS_LIMIT); + + let processes_limit = env::var("PLAYGROUND_PROCESSES_LIMIT") + .ok() + .and_then(|l| l.parse().ok()) + .unwrap_or(DEFAULT_PROCESSES_LIMIT); + + let limits = Arc::new(limits::Global::with_lifecycle( + coordinators_limit, + processes_limit, + LifecycleMetrics, + )); + + Self { + address, + cors_enabled, + gh_token, + metrics_token, + feature_flags, + request_db_path, + anneal_prewarm_stable, + websocket_config, + limits, + port, + root, + } + } + + fn root_path(&self) -> &Path { + &self.root + } + + fn asset_path(&self) -> PathBuf { + self.root.join("assets") + } + + fn use_cors(&self) -> bool { + self.cors_enabled + } + + fn metrics_token(&self) -> Option { + self.metrics_token.as_deref().map(MetricsToken::new) + } + + fn github_token(&self) -> GhToken { + GhToken::new(&self.gh_token) + } + + fn request_database(&self) -> request_database::Database { + use request_database::Database; + + let request_db = match &self.request_db_path { + Some(path) => Database::initialize(path), + None => Database::initialize_memory(), + }; + + request_db.expect("Unable to open request log database") + } + + fn coordinator_factory(&self) -> CoordinatorFactory { + CoordinatorFactory::new(self.limits.clone()) + } + + fn server_socket_addr(&self) -> SocketAddr { + let address = self.address.parse().expect("Invalid address"); + SocketAddr::new(address, self.port) + } +} + +#[derive(Debug, Clone)] +struct GhToken(Option>); + +impl GhToken { + fn new(token: &Option) -> Self { + GhToken(token.clone().map(Arc::new)) + } +} + +#[derive(Debug, Clone)] +struct MetricsToken(Arc); + +impl MetricsToken { + fn new(token: impl Into) -> Self { + MetricsToken(Arc::new(token.into())) + } +} + +#[derive(Debug, Copy, Clone)] +struct LifecycleMetrics; + +impl limits::Lifecycle for LifecycleMetrics { + fn container_start(&self) { + metrics::CONTAINER_QUEUE.inc(); + } + + fn container_acquired(&self, how: limits::Acquisition) { + metrics::CONTAINER_QUEUE.dec(); + + if how == Acquisition::Acquired { + metrics::CONTAINER_ACTIVE.inc(); + } + } + + fn container_release(&self) { + metrics::CONTAINER_ACTIVE.dec(); + } + + fn process_start(&self) { + metrics::PROCESS_QUEUE.inc(); + } + + fn process_acquired(&self, how: limits::Acquisition) { + metrics::PROCESS_QUEUE.dec(); + + if how == Acquisition::Acquired { + metrics::PROCESS_ACTIVE.inc(); + } + } + + fn process_release(&self) { + metrics::PROCESS_ACTIVE.dec(); + } +} + +#[derive(Debug, Copy, Clone)] +struct WebSocketConfig { + /// How long the handshake may take before we cancel it + handshake_timeout: Duration, + /// How long a container has to be unused before we shut it down + idle_timeout: Duration, + /// How long we attempt to idle the container before we error + idle_shutdown_timeout: Duration, + /// How long the user can be connected to the WebSocket + session_timeout: Duration, + /// How long we attempt to shut down the container before we error + shutdown_timeout: Duration, +} + +impl WebSocketConfig { + fn overall_timeout(&self) -> Duration { + const WIGGLE_ROOM: Duration = Duration::from_secs(15); + + self.handshake_timeout + self.session_timeout + WIGGLE_ROOM + } +} diff --git a/anneal/v1/playground/rust-anneal-playground/ui/src/metrics.rs b/anneal/v1/playground/rust-anneal-playground/ui/src/metrics.rs new file mode 100644 index 0000000000..508887cd98 --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/src/metrics.rs @@ -0,0 +1,357 @@ +use orchestrator::coordinator::{self, Channel, CompileTarget, CrateType, Edition, Mode}; +use prometheus::{ + register_histogram, register_histogram_vec, register_int_counter, register_int_counter_vec, + register_int_gauge, Histogram, HistogramVec, IntCounter, IntCounterVec, IntGauge, +}; +use std::{ + future::Future, + sync::LazyLock, + time::{Duration, Instant}, +}; + +pub(crate) static REQUESTS: LazyLock = LazyLock::new(|| { + register_histogram_vec!( + "playground_request_duration_seconds", + "Number of requests made", + Labels::LABELS, + vec![0.1, 1.0, 2.5, 5.0, 10.0, 15.0] + ) + .unwrap() +}); +pub(crate) static LIVE_WS: LazyLock = LazyLock::new(|| { + register_int_gauge!( + "playground_active_websocket_connections_count", + "Number of active WebSocket connections" + ) + .unwrap() +}); +pub(crate) static DURATION_WS: LazyLock = LazyLock::new(|| { + register_histogram!( + "playground_websocket_duration_seconds", + "WebSocket connection length", + vec![15.0, 60.0, 300.0, 600.0, 1800.0, 3600.0, 7200.0] + ) + .unwrap() +}); +pub(crate) static UNAVAILABLE_WS: LazyLock = LazyLock::new(|| { + register_int_counter!( + "playground_websocket_unavailability_count", + "Number of failed WebSocket connections" + ) + .unwrap() +}); +pub(crate) static WS_INCOMING: LazyLock = LazyLock::new(|| { + register_int_counter!( + "playground_websocket_incoming_messages_count", + "Number of WebSocket messages received" + ) + .unwrap() +}); +pub(crate) static WS_OUTGOING: LazyLock = LazyLock::new(|| { + register_int_counter_vec!( + "playground_websocket_outgoing_messages_count", + "Number of WebSocket messages sent", + &["success"], + ) + .unwrap() +}); +pub(crate) static CONTAINER_QUEUE: LazyLock = LazyLock::new(|| { + register_int_gauge!("playground_container_queue", "Number of waiters for a container").unwrap() +}); +pub(crate) static CONTAINER_ACTIVE: LazyLock = LazyLock::new(|| { + register_int_gauge!("playground_container_active", "Number of active containers").unwrap() +}); +pub(crate) static PROCESS_QUEUE: LazyLock = LazyLock::new(|| { + register_int_gauge!("playground_process_queue", "Number of waiters for a process").unwrap() +}); +pub(crate) static PROCESS_ACTIVE: LazyLock = LazyLock::new(|| { + register_int_gauge!("playground_process_active", "Number of active processs").unwrap() +}); + +#[derive(Debug, Copy, Clone, strum::IntoStaticStr)] +pub(crate) enum Endpoint { + Compile, + Execute, + Format, + Miri, + Clippy, + MacroExpansion, + MetaCrates, + MetaVersions, + Evaluate, +} + +#[derive(Debug, Copy, Clone, strum::IntoStaticStr)] +pub(crate) enum Outcome { + Success, + ErrorServer, + ErrorTimeoutSoft, + ErrorUserCode, + Abandoned, +} + +pub(crate) struct LabelsCore { + target: Option, + channel: Option, + mode: Option, + edition: Option>, + crate_type: Option, + tests: Option, + backtrace: Option, +} + +#[derive(Debug, Copy, Clone)] +pub(crate) struct Labels { + endpoint: Endpoint, + outcome: Outcome, + + target: Option, + channel: Option, + mode: Option, + edition: Option>, + crate_type: Option, + tests: Option, + backtrace: Option, +} + +impl Labels { + const COUNT: usize = 9; + + const LABELS: &'static [&'static str; Self::COUNT] = &[ + "endpoint", + "outcome", + "target", + "channel", + "mode", + "edition", + "crate_type", + "tests", + "backtrace", + ]; + + fn as_values(&self) -> [&'static str; Self::COUNT] { + let Self { + endpoint, + outcome, + target, + channel, + mode, + edition, + crate_type, + tests, + backtrace, + } = *self; + + fn b(v: Option) -> &'static str { + v.map_or("", |v| if v { "true" } else { "false" }) + } + + let target = match target { + Some(CompileTarget::Assembly(_, _, _)) => "Assembly", + Some(CompileTarget::Hir) => "Hir", + Some(CompileTarget::LlvmIr) => "LlvmIr", + Some(CompileTarget::Mir) => "Mir", + Some(CompileTarget::Wasm) => "Wasm", + None => "", + }; + let channel = match channel { + Some(Channel::Stable) => "Stable", + Some(Channel::Beta) => "Beta", + Some(Channel::Nightly) => "Nightly", + None => "", + }; + let mode = match mode { + Some(Mode::Debug) => "Debug", + Some(Mode::Release) => "Release", + None => "", + }; + let edition = match edition { + None => "", + Some(None) => "Unspecified", + Some(Some(Edition::Rust2015)) => "Rust2015", + Some(Some(Edition::Rust2018)) => "Rust2018", + Some(Some(Edition::Rust2021)) => "Rust2021", + Some(Some(Edition::Rust2024)) => "Rust2024", + }; + let crate_type = match crate_type { + Some(CrateType::Binary) => "Binary", + Some(CrateType::Library(_)) => "Library", + None => "", + }; + let tests = b(tests); + let backtrace = b(backtrace); + + [ + endpoint.into(), + outcome.into(), + target, + channel, + mode, + edition, + crate_type, + tests, + backtrace, + ] + } + + pub(crate) fn complete(endpoint: Endpoint, labels_core: LabelsCore, outcome: Outcome) -> Self { + let LabelsCore { target, channel, mode, edition, crate_type, tests, backtrace } = + labels_core; + Self { endpoint, outcome, target, channel, mode, edition, crate_type, tests, backtrace } + } +} + +pub(crate) async fn track_metric_no_request_async( + endpoint: Endpoint, + body: B, +) -> Result +where + B: FnOnce() -> Fut, + Fut: Future>, +{ + let start = Instant::now(); + let response = body().await; + let elapsed = start.elapsed(); + + let outcome = if response.is_ok() { Outcome::Success } else { Outcome::ErrorServer }; + let labels = Labels { + endpoint, + outcome, + target: None, + channel: None, + mode: None, + edition: None, + crate_type: None, + tests: None, + backtrace: None, + }; + + record_metric_complete(labels, elapsed); + + response +} + +pub(crate) trait HasLabelsCore { + fn labels_core(&self) -> LabelsCore; +} + +impl HasLabelsCore for coordinator::CompileRequest { + fn labels_core(&self) -> LabelsCore { + let Self { target, channel, crate_type, mode, edition, tests, backtrace, code: _ } = *self; + + LabelsCore { + target: Some(target), + channel: Some(channel), + mode: Some(mode), + edition: Some(Some(edition)), + crate_type: Some(crate_type), + tests: Some(tests), + backtrace: Some(backtrace), + } + } +} + +impl HasLabelsCore for coordinator::ExecuteRequest { + fn labels_core(&self) -> LabelsCore { + let Self { + channel, + crate_type, + mode, + edition, + tests, + backtrace, + code: _, + execution_tool: _, + } = *self; + + LabelsCore { + target: None, + channel: Some(channel), + mode: Some(mode), + edition: Some(Some(edition)), + crate_type: Some(crate_type), + tests: Some(tests), + backtrace: Some(backtrace), + } + } +} + +impl HasLabelsCore for coordinator::FormatRequest { + fn labels_core(&self) -> LabelsCore { + let Self { channel, crate_type, edition, code: _ } = *self; + + LabelsCore { + target: None, + channel: Some(channel), + mode: None, + edition: Some(Some(edition)), + crate_type: Some(crate_type), + tests: None, + backtrace: None, + } + } +} + +impl HasLabelsCore for coordinator::ClippyRequest { + fn labels_core(&self) -> LabelsCore { + let Self { channel, crate_type, edition, code: _ } = *self; + + LabelsCore { + target: None, + channel: Some(channel), + mode: None, + edition: Some(Some(edition)), + crate_type: Some(crate_type), + tests: None, + backtrace: None, + } + } +} + +impl HasLabelsCore for coordinator::MiriRequest { + fn labels_core(&self) -> LabelsCore { + let Self { channel, crate_type, edition, tests, aliasing_model: _, code: _ } = *self; + + LabelsCore { + target: None, + channel: Some(channel), + mode: None, + edition: Some(Some(edition)), + crate_type: Some(crate_type), + tests: Some(tests), + backtrace: None, + } + } +} + +impl HasLabelsCore for coordinator::MacroExpansionRequest { + fn labels_core(&self) -> LabelsCore { + let Self { channel, crate_type, edition, code: _ } = *self; + + LabelsCore { + target: None, + channel: Some(channel), + mode: None, + edition: Some(Some(edition)), + crate_type: Some(crate_type), + tests: None, + backtrace: None, + } + } +} + +pub(crate) fn record_metric( + endpoint: Endpoint, + labels_core: LabelsCore, + outcome: Outcome, + elapsed: Duration, +) { + let labels = Labels::complete(endpoint, labels_core, outcome); + record_metric_complete(labels, elapsed) +} + +fn record_metric_complete(labels: Labels, elapsed: Duration) { + let values = &labels.as_values(); + let histogram = REQUESTS.with_label_values(values); + histogram.observe(elapsed.as_secs_f64()); +} diff --git a/anneal/v1/playground/rust-anneal-playground/ui/src/public_http_api.rs b/anneal/v1/playground/rust-anneal-playground/ui/src/public_http_api.rs new file mode 100644 index 0000000000..da1292d80a --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/src/public_http_api.rs @@ -0,0 +1,207 @@ +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct ErrorJson { + pub(crate) error: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct CompileRequest { + pub(crate) target: String, + #[serde(rename = "assemblyFlavor")] + pub(crate) assembly_flavor: Option, + #[serde(rename = "demangleAssembly")] + pub(crate) demangle_assembly: Option, + #[serde(rename = "processAssembly")] + pub(crate) process_assembly: Option, + pub(crate) channel: String, + pub(crate) mode: String, + #[serde(default)] + pub(crate) edition: String, + #[serde(rename = "crateType")] + pub(crate) crate_type: String, + pub(crate) tests: bool, + #[serde(default)] + pub(crate) backtrace: bool, + pub(crate) code: String, +} + +pub(crate) fn default_execution_tool() -> String { + "cargo".to_owned() +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct CompileResponse { + pub(crate) success: bool, + #[serde(rename = "exitDetail")] + pub(crate) exit_detail: String, + pub(crate) code: String, + pub(crate) stdout: String, + pub(crate) stderr: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct ExecuteRequest { + pub(crate) channel: String, + pub(crate) mode: String, + #[serde(default)] + pub(crate) edition: String, + #[serde(rename = "crateType")] + pub(crate) crate_type: String, + pub(crate) tests: bool, + #[serde(default)] + pub(crate) backtrace: bool, + pub(crate) code: String, + #[serde(default = "default_execution_tool", rename = "executionTool")] + pub(crate) execution_tool: String, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct ExecuteResponse { + pub(crate) success: bool, + #[serde(rename = "exitDetail")] + pub(crate) exit_detail: String, + pub(crate) stdout: String, + pub(crate) stderr: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct FormatRequest { + #[serde(default)] + pub(crate) channel: Option, + #[serde(default)] + pub(crate) edition: String, + pub(crate) code: String, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FormatResponse { + pub(crate) success: bool, + #[serde(rename = "exitDetail")] + pub(crate) exit_detail: String, + pub(crate) code: String, + pub(crate) stdout: String, + pub(crate) stderr: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct ClippyRequest { + #[serde(default)] + pub(crate) channel: Option, + #[serde(default = "default_crate_type", rename = "crateType")] + pub(crate) crate_type: String, + #[serde(default)] + pub(crate) edition: String, + pub(crate) code: String, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct ClippyResponse { + pub(crate) success: bool, + pub(crate) exit_detail: String, + pub(crate) stdout: String, + pub(crate) stderr: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct MiriRequest { + pub(crate) code: String, + #[serde(default)] + pub(crate) edition: String, + #[serde(default)] + pub(crate) tests: bool, + #[serde(default, rename = "aliasingModel")] + pub(crate) aliasing_model: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct MiriResponse { + pub(crate) success: bool, + pub(crate) exit_detail: String, + pub(crate) stdout: String, + pub(crate) stderr: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct MacroExpansionRequest { + pub(crate) code: String, + #[serde(default)] + pub(crate) edition: String, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct MacroExpansionResponse { + pub(crate) success: bool, + pub(crate) exit_detail: String, + pub(crate) stdout: String, + pub(crate) stderr: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub(crate) struct CrateInformation { + pub(crate) name: String, + pub(crate) version: String, + pub(crate) id: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub(crate) struct MetaCratesResponse { + pub(crate) crates: Arc<[CrateInformation]>, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub(crate) struct MetaVersionsResponse { + pub(crate) stable: MetaChannelVersionResponse, + pub(crate) beta: MetaChannelVersionResponse, + pub(crate) nightly: MetaChannelVersionResponse, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub(crate) struct MetaChannelVersionResponse { + pub(crate) rustc: MetaVersionResponse, + pub(crate) rustfmt: MetaVersionResponse, + pub(crate) clippy: MetaVersionResponse, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) miri: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub(crate) struct MetaVersionResponse { + pub(crate) version: Arc, + pub(crate) hash: Arc, + pub(crate) date: Arc, +} + +#[derive(Debug, Clone, Deserialize)] +pub(crate) struct MetaGistCreateRequest { + pub(crate) code: String, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct MetaGistResponse { + pub(crate) id: String, + pub(crate) url: String, + pub(crate) code: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct EvaluateRequest { + pub(crate) version: String, + pub(crate) optimize: String, + pub(crate) code: String, + #[serde(default)] + pub(crate) edition: String, + #[serde(default)] + pub(crate) tests: bool, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct EvaluateResponse { + pub(crate) result: String, + pub(crate) error: Option, +} + +fn default_crate_type() -> String { + "bin".into() +} diff --git a/anneal/v1/playground/rust-anneal-playground/ui/src/request_database.rs b/anneal/v1/playground/rust-anneal-playground/ui/src/request_database.rs new file mode 100644 index 0000000000..5314a06630 --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/src/request_database.rs @@ -0,0 +1,261 @@ +use orchestrator::DropErrorDetailsExt; +use rusqlite::Connection; +use snafu::prelude::*; +use std::path::Path; +use tokio::{ + sync::{mpsc, oneshot}, + task, +}; +use tracing::{info, warn, Instrument as _}; + +pub struct Database { + db: Connection, +} + +#[derive(Debug, Copy, Clone)] +pub struct Id(i64); + +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +pub enum How { + Complete = 0, + Abandoned = 1, + Error = 2, +} + +impl Database { + fn new(path: impl AsRef) -> Result { + let db = Connection::open(path).context(CreateSnafu)?; + Ok(Self { db }) + } + + fn new_memory() -> Result { + let db = Connection::open_in_memory().context(CreateMemorySnafu)?; + Ok(Self { db }) + } + + pub fn initialize(path: impl AsRef) -> Result { + let this = Self::new(path)?; + this.ensure_tables()?; + Ok(this) + } + + pub fn initialize_memory() -> Result { + let this = Self::new_memory()?; + this.ensure_tables()?; + Ok(this) + } + + fn ensure_tables(&self) -> Result<()> { + let sql = r#" + CREATE TABLE IF NOT EXISTS requests ( + id INTEGER PRIMARY KEY, + started_at INTEGER DEFAULT (unixepoch()) NOT NULL, + ended_at INTEGER, + how INTEGER, + category TEXT NOT NULL, + payload TEXT NOT NULL + ) STRICT + "#; + self.db.execute_batch(sql).context(InitializeSnafu) + } + + pub fn start_request(&self, category: &str, payload: &str) -> Result { + let sql = r#" + INSERT INTO requests (category, payload) + VALUES (?1, ?2) + RETURNING id + "#; + let id = + self.db.query_row(sql, (category, payload), |r| r.get(0)).context(StartRequestSnafu)?; + + Ok(Id(id)) + } + + pub fn end_request(&self, id: Id, how: How) -> Result<()> { + let sql = r#" + UPDATE requests + SET + ended_at = unixepoch(), + how = ?2 + WHERE id = ?1 + "#; + self.db.execute(sql, (id.0, how as u8)).map(drop).context(EndRequestSnafu) + } + + pub fn spawn(self) -> (task::JoinHandle<()>, Handle) { + let (tx, rx) = mpsc::channel(10); + let task = task::spawn_blocking(|| self.task(rx)); + let handle = Handle { tx }; + + (task, handle) + } + + fn task(self, mut rx: mpsc::Receiver) { + while let Some(msg) = rx.blocking_recv() { + match msg { + Message::StartRequest { category, payload, tx } => { + let r = self.start_request(&category, &payload); + tx.send(r).ok(/* Don't care if caller is gone */); + } + + Message::EndRequest { id, how, tx } => { + let r = self.end_request(id, how); + tx.send(r).ok(/* Don't care if caller is gone */); + } + } + } + } +} + +#[derive(Debug, Snafu)] +pub enum Error { + Create { source: rusqlite::Error }, + + CreateMemory { source: rusqlite::Error }, + + Initialize { source: rusqlite::Error }, + + StartRequest { source: rusqlite::Error }, + + EndRequest { source: rusqlite::Error }, +} + +pub type Result = std::result::Result; + +#[derive(Debug)] +enum Message { + StartRequest { category: String, payload: String, tx: oneshot::Sender> }, + + EndRequest { id: Id, how: How, tx: oneshot::Sender> }, +} + +#[derive(Clone)] +pub struct Handle { + tx: mpsc::Sender, +} + +impl Handle { + async fn start_request( + &self, + category: impl Into, + payload: impl Into, + ) -> HandleResult { + let category = category.into(); + let payload = payload.into(); + let (tx, rx) = oneshot::channel(); + + self.tx + .send(Message::StartRequest { category, payload, tx }) + .await + .drop_error_details() + .context(SendStartRequestSnafu)?; + + let id = rx.await.context(RecvStartRequestSnafu)??; + + info!(request_id = id.0, "Started request"); + + Ok(id) + } + + async fn attempt_start_request( + &self, + category: impl Into, + payload: impl Into, + ) -> Option { + match self.start_request(category, payload).await { + Ok(id) => Some(id), + Err(err) => { + warn!(?err, "Unable to record start request"); + None + } + } + } + + async fn end_request(&self, id: Id, how: How) -> HandleResult<()> { + let (tx, rx) = oneshot::channel(); + + self.tx + .send(Message::EndRequest { id, how, tx }) + .await + .drop_error_details() + .context(SendEndRequestSnafu)?; + + rx.await.context(RecvEndRequestSnafu)??; + + info!(request_id = id.0, "Ended request"); + + Ok(()) + } + + async fn attempt_end_request(&self, id: Id, how: How) { + if let Err(err) = self.end_request(id, how).await { + warn!(?err, "Unable to record end request"); + } + } + + pub async fn start_with_guard( + self, + category: impl Into, + payload: impl Into, + ) -> EndGuard { + let g = self + .attempt_start_request(category, payload) + .await + .map(|id| EndGuardInner(id, How::Abandoned, Some(self))); + EndGuard(g) + } +} + +pub struct EndGuard(Option); + +impl EndGuard { + pub fn complete_now(mut self, result: Result) -> Result { + if let Some(mut inner) = self.0.take() { + inner.1 = if result.is_err() { How::Error } else { How::Complete }; + + drop(inner); + } + + result + } +} + +struct EndGuardInner(Id, How, Option); + +impl Drop for EndGuardInner { + fn drop(&mut self) { + let Self(id, how, ref mut handle) = *self; + if let Ok(h) = tokio::runtime::Handle::try_current() { + if let Some(handle) = handle.take() { + h.spawn(async move { handle.attempt_end_request(id, how).await }.in_current_span()); + } + } + } +} + +#[derive(Debug, Snafu)] +pub enum HandleError { + #[snafu(transparent)] + Database { + source: Error, + }, + + SendStartRequest { + source: mpsc::error::SendError<()>, + }, + + RecvStartRequest { + source: oneshot::error::RecvError, + }, + + SendEndRequest { + source: mpsc::error::SendError<()>, + }, + + RecvEndRequest { + source: oneshot::error::RecvError, + }, +} + +pub type HandleResult = std::result::Result; diff --git a/anneal/v1/playground/rust-anneal-playground/ui/src/server_axum.rs b/anneal/v1/playground/rust-anneal-playground/ui/src/server_axum.rs new file mode 100644 index 0000000000..eb15a93b7c --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/src/server_axum.rs @@ -0,0 +1,1547 @@ +use crate::{ + gist, + metrics::{ + record_metric, track_metric_no_request_async, Endpoint, HasLabelsCore, Outcome, + UNAVAILABLE_WS, + }, + request_database::Handle, + Config, GhToken, MetricsToken, WebSocketConfig, +}; +use axum::{ + body::Body, + extract::{self, ws::WebSocketUpgrade, Extension, Path}, + handler::Handler, + http::{ + header, request::Parts, uri::PathAndQuery, HeaderName, HeaderValue, Method, Request, + StatusCode, Uri, + }, + middleware, + response::IntoResponse, + routing::{get, get_service, post, MethodRouter}, + Router, +}; +use axum_extra::{ + headers::{authorization::Bearer, Authorization, CacheControl, ETag, IfNoneMatch}, + TypedHeader, +}; +use futures::{FutureExt, TryFutureExt}; +use orchestrator::coordinator::{self, CoordinatorFactory, DockerBackend, TRACKED_CONTAINERS}; +use snafu::prelude::*; +use std::{ + convert::TryInto, + mem, path, + str::FromStr, + sync::{Arc, LazyLock}, + time::{Duration, Instant, UNIX_EPOCH}, +}; +use tokio::{ + select, + sync::{mpsc, Mutex}, +}; +use tower_http::{ + cors::{self, CorsLayer}, + request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer}, + services::ServeDir, + set_header::SetResponseHeader, + trace::TraceLayer, +}; +use tracing::{error, error_span, field, info, warn}; + +use crate::{env::PLAYGROUND_GITHUB_TOKEN, public_http_api as api}; + +use cache::{ + cache_task, CacheTaskItem, CacheTx, CacheTxError, Stamped, SANDBOX_CACHE_TIME_TO_LIVE, +}; + +const ONE_HOUR: Duration = Duration::from_secs(60 * 60); + +const CORS_CACHE_TIME_TO_LIVE: Duration = ONE_HOUR; + +const MAX_AGE_ONE_DAY: HeaderValue = HeaderValue::from_static("public, max-age=86400"); +const MAX_AGE_ONE_YEAR: HeaderValue = HeaderValue::from_static("public, max-age=31536000"); + +const DOCKER_PROCESS_TIMEOUT_SOFT: Duration = Duration::from_secs(10); +const ANNEAL_VERIFY_PROCESS_TIMEOUT_SOFT: Duration = Duration::from_secs(120); + +mod cache; +mod websocket; + +#[derive(Debug, Clone)] +struct Factory(Arc); + +#[derive(Clone)] +pub(crate) struct AnnealPrewarm { + coordinator: Arc>>>, + enabled: bool, +} + +impl AnnealPrewarm { + fn new(enabled: bool) -> Self { + Self { coordinator: Arc::new(Mutex::new(None)), enabled } + } + + fn spawn_if_enabled(&self, factory: Arc) { + if !self.enabled { + return; + } + + let prewarm = self.clone(); + tokio::spawn(async move { + prewarm.run(factory).await; + }); + } + + async fn run(self, factory: Arc) { + let coordinator = factory.build::(); + let start = Instant::now(); + info!("[anneal-prewarm] starting stable Anneal prewarm"); + + let response = tokio::time::timeout( + ANNEAL_VERIFY_PROCESS_TIMEOUT_SOFT, + coordinator.execute(anneal_prewarm_request()), + ) + .await; + + match response { + Ok(Ok(output)) if output.response.success => { + let cargo_anneal_verify_ms = extract_prewarm_cargo_anneal_verify_ms(&output.stdout) + .or_else(|| extract_prewarm_cargo_anneal_verify_ms(&output.stderr)); + info!( + elapsed_ms = start.elapsed().as_millis(), + cargo_anneal_verify_ms = cargo_anneal_verify_ms.unwrap_or(0), + cargo_anneal_verify_ms_found = cargo_anneal_verify_ms.is_some(), + "[anneal-prewarm] stable Anneal prewarm finished" + ); + *self.coordinator.lock().await = Some(coordinator); + } + + Ok(Ok(output)) => { + warn!( + elapsed_ms = start.elapsed().as_millis(), + exit_detail = %output.response.exit_detail, + "[anneal-prewarm] stable Anneal prewarm failed" + ); + if let Err(e) = coordinator.shutdown().await { + warn!(error = ?e, "[anneal-prewarm] failed to shut down failed prewarm coordinator"); + } + } + + Ok(Err(e)) => { + warn!( + elapsed_ms = start.elapsed().as_millis(), + error = ?e, + "[anneal-prewarm] stable Anneal prewarm errored" + ); + if let Err(e) = coordinator.shutdown().await { + warn!(error = ?e, "[anneal-prewarm] failed to shut down errored prewarm coordinator"); + } + } + + Err(_) => { + warn!( + elapsed_ms = start.elapsed().as_millis(), + "[anneal-prewarm] stable Anneal prewarm timed out" + ); + if let Err(e) = coordinator.shutdown().await { + warn!(error = ?e, "[anneal-prewarm] failed to shut down timed-out prewarm coordinator"); + } + } + } + } + + pub(crate) async fn take(&self) -> Option> { + let coordinator = self.coordinator.lock().await.take(); + if self.enabled && coordinator.is_none() { + info!("[anneal-prewarm] no prewarmed coordinator ready for WebSocket"); + } + coordinator + } +} + +fn anneal_prewarm_request() -> coordinator::ExecuteRequest { + coordinator::ExecuteRequest { + channel: coordinator::Channel::Stable, + mode: coordinator::Mode::Debug, + edition: coordinator::Edition::Rust2021, + crate_type: coordinator::CrateType::Binary, + tests: false, + backtrace: false, + code: ANNEAL_PREWARM_CODE.to_owned(), + execution_tool: coordinator::ExecutionTool::AnnealVerify, + } +} + +const ANNEAL_PREWARM_CODE: &str = r#"/// ```anneal, unsafe(axiom) +/// ``` +pub unsafe fn anneal_warmup_identity(x: u32) -> u32 { + x +} + +fn main() {} +"#; + +fn extract_prewarm_cargo_anneal_verify_ms(output: &str) -> Option { + output + .lines() + .find(|line| { + line.contains("[anneal] verification succeeded in") + || line.contains("[anneal] verification failed after") + }) + .and_then(|line| line.split_whitespace().rev().nth(1)) + .and_then(|ms| ms.parse().ok()) +} + +pub(crate) async fn serve(config: Config) { + let factory = Arc::new(config.coordinator_factory()); + let anneal_prewarm = AnnealPrewarm::new(config.anneal_prewarm_stable); + + let (cache_crates_task, cache_crates_tx) = + CacheTx::spawn(|rx| cache_crates_task(factory.clone(), rx)); + let (cache_versions_task, cache_versions_tx) = + CacheTx::spawn(|rx| cache_versions_task(factory.clone(), rx)); + + let axum_factory = Factory(factory.clone()); + + let request_db = config.request_database(); + let (db_task, db_handle) = request_db.spawn(); + + let root_files = static_file_service(config.root_path(), MAX_AGE_ONE_DAY); + let asset_files = static_file_service(config.asset_path(), MAX_AGE_ONE_YEAR); + let rewrite_help_as_index = middleware::from_fn(rewrite_help_as_index); + + let mut app = Router::new() + .fallback_service(root_files) + .nest_service("/assets", asset_files) + .layer(rewrite_help_as_index) + .route("/evaluate.json", post(evaluate)) + .route("/compile", post(compile)) + .route("/execute", post(execute)) + .route("/format", post(format)) + .route("/clippy", post(clippy)) + .route("/miri", post(miri)) + .route("/macro-expansion", post(macro_expansion)) + .route("/meta/crates", get_or_post(meta_crates)) + .route("/meta/versions", get(meta_versions)) + .route("/meta/gist", post(meta_gist_create)) + .route("/meta/gist/", post(meta_gist_create)) // compatibility with lax frontend code + .route("/meta/gist/{id}", get(meta_gist_get)) + .route("/metrics", get(metrics)) + .route("/tokio-metrics", get(tokio_metrics)) + .route("/websocket", get(websocket)) + .route("/nowebsocket", post(nowebsocket)) + .route("/internal/debug/whynowebsocket", get(whynowebsocket)) + .route("/internal/debug/tracked-containers", get(tracked_containers)) + .layer(Extension(axum_factory)) + .layer(Extension(db_handle)) + .layer(Extension(anneal_prewarm.clone())) + .layer(Extension(cache_crates_tx)) + .layer(Extension(cache_versions_tx)) + .layer(Extension(config.github_token())) + .layer(Extension(config.feature_flags)) + .layer(Extension(config.websocket_config)); + + if let Some(token) = config.metrics_token() { + app = app.layer(Extension(token)) + } + + if config.use_cors() { + app = app.layer({ + CorsLayer::new() + .allow_origin(cors::Any) + .allow_headers([header::CONTENT_TYPE]) + .allow_methods([Method::GET, Method::POST]) + .allow_credentials(false) + .max_age(CORS_CACHE_TIME_TO_LIVE) + }); + } + + let x_request_id = HeaderName::from_static("x-request-id"); + + // Basic access logging + app = app.layer({ + let x_request_id = x_request_id.clone(); + TraceLayer::new_for_http().make_span_with(move |req: &Request<_>| { + const REQUEST_ID: &str = "request_id"; + + let method = req.method(); + let uri = req.uri(); + let request_id = req.headers().get(&x_request_id).and_then(|id| id.to_str().ok()); + + let span = error_span!("request", %method, %uri, { REQUEST_ID } = field::Empty); + + if let Some(request_id) = request_id { + span.record(REQUEST_ID, field::display(request_id)); + } + + span + }) + }); + + // propagate `x-request-id` headers from request to response + app = app.layer(PropagateRequestIdLayer::new(x_request_id.clone())); + + app = app.layer(SetRequestIdLayer::new(x_request_id.clone(), MakeRequestUuid)); + + let server_socket_addr = config.server_socket_addr(); + tracing::info!("Serving playground backend at http://{server_socket_addr}"); + + let listener = tokio::net::TcpListener::bind(server_socket_addr).await.unwrap(); + anneal_prewarm.spawn_if_enabled(factory.clone()); + + let server = axum::serve(listener, app.into_make_service()); + + select! { + v = server => v.unwrap(), + v = db_task => v.unwrap(), + v = cache_crates_task => v.unwrap(), + v = cache_versions_task => v.unwrap(), + } +} + +fn get_or_post(handler: impl Handler + Copy) -> MethodRouter { + get(handler).post(handler) +} + +fn static_file_service(root: impl AsRef, max_age: HeaderValue) -> MethodRouter { + let files = ServeDir::new(root).precompressed_gzip(); + + let with_caching = SetResponseHeader::if_not_present(files, header::CACHE_CONTROL, max_age); + + get_service(with_caching) +} + +async fn rewrite_help_as_index( + mut req: Request, + next: middleware::Next, +) -> impl IntoResponse { + let uri = req.uri_mut(); + if uri.path() == "/help" { + let rewritten_uri = mem::take(uri); + let mut parts = rewritten_uri.into_parts(); + parts.path_and_query = Some(PathAndQuery::from_static("/index.html")); + *uri = Uri::from_parts(parts).unwrap(); + } + next.run(req).await +} + +async fn attempt_record_request( + db: Handle, + req: R, + f: impl AsyncFnOnce(R) -> Result, +) -> Result +where + R: HasEndpoint + serde::Serialize, +{ + let category = format!("http.{}", <&str>::from(R::ENDPOINT)); + let payload = serde_json::to_string(&req).unwrap_or_else(|_| String::from("")); + let guard = db.start_with_guard(category, payload).await; + + let r = f(req).await; + + guard.complete_now(r) +} + +// This is a backwards compatibilty shim. The Rust documentation uses +// this to run code in place. +async fn evaluate( + Extension(factory): Extension, + Extension(db): Extension, + Json(req): Json, +) -> Result> { + attempt_record_request(db, req, async |req| { + with_coordinator(&factory.0, req, async |c, req| { + c.execute(req).context(EvaluateSnafu).await + }) + .await + .map(Json) + }) + .await +} + +async fn compile( + Extension(factory): Extension, + Extension(db): Extension, + Json(req): Json, +) -> Result> { + attempt_record_request(db, req, async |req| { + with_coordinator(&factory.0, req, async |c, req| c.compile(req).context(CompileSnafu).await) + .await + .map(Json) + }) + .await +} + +async fn execute( + Extension(factory): Extension, + Extension(db): Extension, + Json(req): Json, +) -> Result> { + attempt_record_request(db, req, async |req| { + with_coordinator(&factory.0, req, async |c, req| c.execute(req).context(ExecuteSnafu).await) + .await + .map(Json) + }) + .await +} + +async fn format( + Extension(factory): Extension, + Extension(db): Extension, + Json(req): Json, +) -> Result> { + attempt_record_request(db, req, async |req| { + with_coordinator(&factory.0, req, async |c, req| c.format(req).context(FormatSnafu).await) + .await + .map(Json) + }) + .await +} + +async fn clippy( + Extension(factory): Extension, + Extension(db): Extension, + Json(req): Json, +) -> Result> { + attempt_record_request(db, req, async |req| { + with_coordinator(&factory.0, req, async |c, req| c.clippy(req).context(ClippySnafu).await) + .await + .map(Json) + }) + .await +} + +async fn miri( + Extension(factory): Extension, + Extension(db): Extension, + Json(req): Json, +) -> Result> { + attempt_record_request(db, req, async |req| { + with_coordinator(&factory.0, req, async |c, req| c.miri(req).context(MiriSnafu).await) + .await + .map(Json) + }) + .await +} + +async fn macro_expansion( + Extension(factory): Extension, + Extension(db): Extension, + Json(req): Json, +) -> Result> { + attempt_record_request(db, req, async |req| { + with_coordinator(&factory.0, req, async |c, req| { + c.macro_expansion(req).context(MacroExpansionSnafu).await + }) + .await + .map(Json) + }) + .await +} + +pub(crate) trait HasEndpoint { + const ENDPOINT: Endpoint; +} + +impl HasEndpoint for api::EvaluateRequest { + const ENDPOINT: Endpoint = Endpoint::Evaluate; +} + +impl HasEndpoint for api::CompileRequest { + const ENDPOINT: Endpoint = Endpoint::Compile; +} + +impl HasEndpoint for api::ExecuteRequest { + const ENDPOINT: Endpoint = Endpoint::Execute; +} + +impl HasEndpoint for api::FormatRequest { + const ENDPOINT: Endpoint = Endpoint::Format; +} + +impl HasEndpoint for api::ClippyRequest { + const ENDPOINT: Endpoint = Endpoint::Clippy; +} + +impl HasEndpoint for api::MiriRequest { + const ENDPOINT: Endpoint = Endpoint::Miri; +} + +impl HasEndpoint for api::MacroExpansionRequest { + const ENDPOINT: Endpoint = Endpoint::MacroExpansion; +} + +trait IsSuccess { + fn is_success(&self) -> bool; +} + +trait HasProcessTimeout { + fn process_timeout(&self) -> Duration { + DOCKER_PROCESS_TIMEOUT_SOFT + } +} + +impl IsSuccess for &T +where + T: IsSuccess, +{ + fn is_success(&self) -> bool { + T::is_success(self) + } +} + +impl IsSuccess for coordinator::WithOutput +where + T: IsSuccess, +{ + fn is_success(&self) -> bool { + self.response.is_success() + } +} + +impl IsSuccess for coordinator::CompileResponse { + fn is_success(&self) -> bool { + self.success + } +} + +impl HasProcessTimeout for coordinator::CompileRequest {} + +impl IsSuccess for coordinator::ExecuteResponse { + fn is_success(&self) -> bool { + self.success + } +} + +impl HasProcessTimeout for coordinator::ExecuteRequest { + fn process_timeout(&self) -> Duration { + match self.execution_tool { + coordinator::ExecutionTool::AnnealVerify => ANNEAL_VERIFY_PROCESS_TIMEOUT_SOFT, + coordinator::ExecutionTool::Cargo => DOCKER_PROCESS_TIMEOUT_SOFT, + } + } +} + +impl IsSuccess for coordinator::FormatResponse { + fn is_success(&self) -> bool { + self.success + } +} + +impl HasProcessTimeout for coordinator::FormatRequest {} + +impl IsSuccess for coordinator::ClippyResponse { + fn is_success(&self) -> bool { + self.success + } +} + +impl HasProcessTimeout for coordinator::ClippyRequest {} + +impl IsSuccess for coordinator::MiriResponse { + fn is_success(&self) -> bool { + self.success + } +} + +impl HasProcessTimeout for coordinator::MiriRequest {} + +impl IsSuccess for coordinator::MacroExpansionResponse { + fn is_success(&self) -> bool { + self.success + } +} + +impl HasProcessTimeout for coordinator::MacroExpansionRequest {} + +impl Outcome { + fn from_success(other: impl IsSuccess) -> Self { + if other.is_success() { + Outcome::Success + } else { + Outcome::ErrorUserCode + } + } +} + +async fn with_coordinator( + factory: &CoordinatorFactory, + req: WebReq, + f: impl AsyncFnOnce(&coordinator::Coordinator, Req) -> Result, +) -> Result +where + WebReq: TryInto, + WebReq: HasEndpoint, + Error: From, + Req: HasLabelsCore + HasProcessTimeout, + Resp: Into, + Resp: IsSuccess, +{ + let coordinator = factory.build(); + + let job = async { + let req = req.try_into()?; + + let labels_core = req.labels_core(); + let process_timeout = req.process_timeout(); + + let start = Instant::now(); + + let job = f(&coordinator, req); + let resp = tokio::time::timeout(process_timeout, job).await; + + let elapsed = start.elapsed(); + + let outcome = match &resp { + Ok(Ok(v)) => Outcome::from_success(v), + Ok(Err(_)) => Outcome::ErrorServer, + Err(_) => Outcome::ErrorTimeoutSoft, + }; + + // Note that any early return before this point won't be + // reported in the metrics! + + record_metric(WebReq::ENDPOINT, labels_core, outcome, elapsed); + + let resp = resp.context(TimeoutSnafu)?; + + resp.map(Into::into) + }; + + let resp = job.await; + + coordinator.shutdown().await.context(ShutdownCoordinatorSnafu)?; + + resp +} + +async fn meta_crates( + Extension(tx): Extension, + if_none_match: Option>, +) -> Result { + let value = track_metric_no_request_async(Endpoint::MetaCrates, || tx.get()) + .await + .context(CratesSnafu)?; + apply_timestamped_caching(value, if_none_match) +} + +async fn meta_versions( + Extension(tx): Extension, + if_none_match: Option>, +) -> Result { + let value = track_metric_no_request_async(Endpoint::MetaVersions, || tx.get()) + .await + .context(VersionsSnafu)?; + apply_timestamped_caching(value, if_none_match) +} + +fn apply_timestamped_caching( + value: Stamped, + if_none_match: Option>, +) -> Result +where + Json: IntoResponse, +{ + let (value, timestamp) = value; + + let timestamp = timestamp.duration_since(UNIX_EPOCH).unwrap(); + let etag = format!(r#""pg-ts-{}""#, timestamp.as_secs()); + let etag = ETag::from_str(&etag).unwrap(); + + let cache_control = CacheControl::new().with_max_age(SANDBOX_CACHE_TIME_TO_LIVE).with_public(); + + let use_fresh = + if_none_match.is_none_or(|if_none_match| if_none_match.0.precondition_passes(&etag)); + + let etag = TypedHeader(etag); + let cache_control = TypedHeader(cache_control); + + let response = if use_fresh { + (StatusCode::OK, Json(value)).into_response() + } else { + StatusCode::NOT_MODIFIED.into_response() + }; + + Ok((etag, cache_control, response)) +} + +fn must_get(token: &GhToken) -> Result { + token.0.as_ref().map(|s| String::clone(s)).context(NoGithubTokenSnafu) +} + +async fn meta_gist_create( + Extension(token): Extension, + Json(req): Json, +) -> Result> { + let token = must_get(&token)?; + gist::create_future(token, req.code).await.map(Into::into).map(Json).context(GistCreationSnafu) +} + +async fn meta_gist_get( + Extension(token): Extension, + Path(id): Path, +) -> Result> { + let token = must_get(&token)?; + gist::load_future(token, &id).await.map(Into::into).map(Json).context(GistLoadingSnafu) +} + +async fn metrics(_: MetricsAuthorization) -> Result { + use prometheus::{Encoder, TextEncoder}; + + let metric_families = prometheus::gather(); + let encoder = TextEncoder::new(); + let mut buffer = Vec::new(); + + encoder + .encode(&metric_families, &mut buffer) + .map(|_| ([(header::CONTENT_TYPE, "text/plain; version=0.0.4; charset=utf-8")], buffer)) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) +} + +async fn tokio_metrics(_: MetricsAuthorization) -> Result { + use std::fmt::Write; + use tokio::runtime::Handle; + + let handle = Handle::try_current().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let metrics = handle.metrics(); + + let mut output = String::new(); + + macro_rules! name_value_pairs { + [$($name:ident),* $(,)?] => { + [$(( + concat!("playground_tokio_", stringify!($name), "_gauge"), + metrics.$name(), + ),)*] + } + } + + let pairs = name_value_pairs![num_workers, num_alive_tasks, global_queue_depth]; + + for (name, value) in pairs { + writeln!(output, "# TYPE {name} gauge").map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + writeln!(output, "{name} {value}").map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } + + writeln!(output, "# EOF").map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let headers = + [(header::CONTENT_TYPE, "application/openmetrics-text; version=1.0.0; charset=utf-8")]; + + Ok((headers, output)) +} + +async fn websocket( + ws: WebSocketUpgrade, + Extension(config): Extension, + Extension(factory): Extension, + Extension(feature_flags): Extension, + Extension(anneal_prewarm): Extension, + Extension(db): Extension, +) -> impl IntoResponse { + ws.on_upgrade(move |s| { + websocket::handle(s, config, factory.0, feature_flags.into(), anneal_prewarm, db) + }) +} + +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct NoWebSocketRequest { + #[serde(default)] + error: String, +} + +async fn nowebsocket(Json(req): Json) { + record_websocket_error(req.error); + UNAVAILABLE_WS.inc(); +} + +static WS_ERRORS: LazyLock>> = + LazyLock::new(Default::default); + +fn record_websocket_error(error: String) { + *WS_ERRORS.lock().unwrap_or_else(|e| e.into_inner()).entry(error).or_default() += 1; +} + +async fn whynowebsocket() -> String { + format!("{:#?}", WS_ERRORS.lock().unwrap_or_else(|e| e.into_inner())) +} + +async fn tracked_containers() -> String { + let tracked_containers = TRACKED_CONTAINERS.lock().unwrap_or_else(|e| e.into_inner()).clone(); + tracked_containers.iter().fold(String::new(), |a, s| a + s + "\n") +} + +#[derive(Debug)] +struct MetricsAuthorization; + +type MetricsAuthorizationRejection = (StatusCode, &'static str); + +impl MetricsAuthorization { + const FAILURE: MetricsAuthorizationRejection = (StatusCode::UNAUTHORIZED, "Wrong credentials"); +} + +type CacheCratesTx = CacheTx; +type CacheCratesItem = CacheTaskItem; + +#[tracing::instrument(skip_all)] +async fn cache_crates_task(factory: Arc, rx: mpsc::Receiver) { + cache_task(rx, move || { + let coordinator = factory.build::(); + + async move { + let crates = coordinator.crates().map_ok(From::from).await?; + + coordinator.shutdown().await?; + + Ok::<_, CacheCratesError>(crates) + } + .boxed() + }) + .await +} + +#[derive(Debug, Snafu)] +enum CacheCratesError { + #[snafu(transparent)] + Crates { source: coordinator::CratesError }, + + #[snafu(transparent)] + Shutdown { source: coordinator::Error }, +} + +type CacheVersionsTx = CacheTx; +type CacheVersionsItem = CacheTaskItem; + +#[tracing::instrument(skip_all)] +async fn cache_versions_task( + factory: Arc, + rx: mpsc::Receiver, +) { + cache_task(rx, move || { + let coordinator = factory.build::(); + + async move { + let versions = coordinator.versions().map_ok(From::from).await?; + + coordinator.shutdown().await?; + + Ok::<_, CacheVersionsError>(versions) + } + .boxed() + }) + .await +} + +#[derive(Debug, Snafu)] +enum CacheVersionsError { + #[snafu(transparent)] + Versions { source: coordinator::VersionsError }, + + #[snafu(transparent)] + Shutdown { source: coordinator::Error }, +} + +impl extract::FromRequestParts for MetricsAuthorization +where + S: Send + Sync, +{ + type Rejection = MetricsAuthorizationRejection; + + async fn from_request_parts(req: &mut Parts, state: &S) -> Result { + match Extension::::from_request_parts(req, state).await { + Ok(Extension(expected)) => { + match TypedHeader::>::from_request_parts(req, state).await { + Ok(TypedHeader(Authorization(actual))) => { + if actual.token() == *expected.0 { + Ok(Self) + } else { + Err(Self::FAILURE) + } + } + Err(_) => Err(Self::FAILURE), + } + } + // If we haven't set a code at all, allow the request. + Err(_) => Ok(Self), + } + } +} + +impl IntoResponse for Error { + fn into_response(self) -> axum::response::Response { + let error = snafu::CleanedErrorText::new(&self) + .map(|(_, s, _)| s) + .reduce(|l, r| l + ": " + &r) + .unwrap_or_default(); + error!(error, "Returning an error to the client"); + let resp = Json(api::ErrorJson { error }); + let resp = (StatusCode::INTERNAL_SERVER_ERROR, resp); + resp.into_response() + } +} + +/// This type only exists so that we can recover from the `axum::Json` +/// error and format it using our expected JSON error object. +struct Json(T); + +impl extract::FromRequest for Json +where + T: serde::de::DeserializeOwned, + S: Send + Sync, +{ + type Rejection = axum::response::Response; + + async fn from_request(req: Request, state: &S) -> Result { + match axum::Json::::from_request(req, state).await { + Ok(v) => Ok(Self(v.0)), + Err(e) => { + let error = format!("Unable to deserialize request: {e}"); + let resp = axum::Json(api::ErrorJson { error }); + let resp = (StatusCode::BAD_REQUEST, resp); + Err(resp.into_response()) + } + } + } +} + +impl IntoResponse for Json +where + T: serde::Serialize, +{ + fn into_response(self) -> axum::response::Response { + axum::Json(self.0).into_response() + } +} + +#[derive(Debug, Snafu)] +enum Error { + #[snafu(display("Gist creation failed"))] + GistCreation { source: octocrab::Error }, + + #[snafu(display("Gist loading failed"))] + GistLoading { source: octocrab::Error }, + + #[snafu(display("{PLAYGROUND_GITHUB_TOKEN} not set up for reading/writing gists"))] + NoGithubToken, + + #[snafu(transparent)] + EvaluateRequest { source: api_orchestrator_integration_impls::ParseEvaluateRequestError }, + + #[snafu(transparent)] + CompileRequest { source: api_orchestrator_integration_impls::ParseCompileRequestError }, + + #[snafu(transparent)] + ExecuteRequest { source: api_orchestrator_integration_impls::ParseExecuteRequestError }, + + #[snafu(transparent)] + FormatRequest { source: api_orchestrator_integration_impls::ParseFormatRequestError }, + + #[snafu(transparent)] + ClippyRequest { source: api_orchestrator_integration_impls::ParseClippyRequestError }, + + #[snafu(transparent)] + MiriRequest { source: api_orchestrator_integration_impls::ParseMiriRequestError }, + + #[snafu(transparent)] + MacroExpansionRequest { + source: api_orchestrator_integration_impls::ParseMacroExpansionRequestError, + }, + + #[snafu(display("Unable to find the available crates"))] + Crates { source: CacheTxError }, + + #[snafu(display("Unable to find the available versions"))] + Versions { source: CacheTxError }, + + #[snafu(display("Unable to shutdown the coordinator"))] + ShutdownCoordinator { source: orchestrator::coordinator::Error }, + + #[snafu(display("Unable to process the evaluate request"))] + Evaluate { source: orchestrator::coordinator::ExecuteError }, + + #[snafu(display("Unable to process the compile request"))] + Compile { source: orchestrator::coordinator::CompileError }, + + #[snafu(display("Unable to process the execute request"))] + Execute { source: orchestrator::coordinator::ExecuteError }, + + #[snafu(display("Unable to process the format request"))] + Format { source: orchestrator::coordinator::FormatError }, + + #[snafu(display("Unable to process the Clippy request"))] + Clippy { source: orchestrator::coordinator::ClippyError }, + + #[snafu(display("Unable to process the Miri request"))] + Miri { source: orchestrator::coordinator::MiriError }, + + #[snafu(display("Unable to process the macro expansion request"))] + MacroExpansion { source: orchestrator::coordinator::MacroExpansionError }, + + #[snafu(display("The operation timed out"))] + Timeout { source: tokio::time::error::Elapsed }, +} + +type Result = ::std::result::Result; + +pub(crate) mod api_orchestrator_integration_impls { + use orchestrator::coordinator::*; + use snafu::prelude::*; + use std::convert::TryFrom; + + use crate::gist; + use crate::public_http_api as api; + + impl From> for api::MetaCratesResponse { + fn from(other: Vec) -> Self { + let crates = other + .into_iter() + .map(|c| { + let Crate { name, version, id } = c; + api::CrateInformation { name, version, id } + }) + .collect(); + Self { crates } + } + } + + impl From for api::MetaVersionsResponse { + fn from(other: Versions) -> Self { + let Versions { stable, beta, nightly } = other; + let [stable, beta, nightly] = [stable, beta, nightly].map(Into::into); + Self { stable, beta, nightly } + } + } + + impl From for api::MetaChannelVersionResponse { + fn from(other: ChannelVersions) -> Self { + let ChannelVersions { rustc, rustfmt, clippy, miri } = other; + let [rustc, rustfmt, clippy] = [rustc, rustfmt, clippy].map(|v| (&v).into()); + let miri = miri.map(|v| (&v).into()); + Self { rustc, rustfmt, clippy, miri } + } + } + + impl From<&Version> for api::MetaVersionResponse { + fn from(other: &Version) -> Self { + Self { + version: (&*other.release).into(), + hash: (&*other.commit_hash).into(), + date: (&*other.commit_date).into(), + } + } + } + + impl TryFrom for ExecuteRequest { + type Error = ParseEvaluateRequestError; + + fn try_from(other: api::EvaluateRequest) -> Result { + let api::EvaluateRequest { version, optimize, code, edition, tests } = other; + + let mode = if optimize != "0" { Mode::Release } else { Mode::Debug }; + + let edition = if edition.trim().is_empty() { + Edition::Rust2015 + } else { + parse_edition(&edition)? + }; + + Ok(ExecuteRequest { + channel: parse_channel(&version)?, + mode, + edition, + crate_type: CrateType::Binary, + tests, + backtrace: false, + code, + execution_tool: ExecutionTool::Cargo, + }) + } + } + + #[derive(Debug, Snafu)] + pub(crate) enum ParseEvaluateRequestError { + #[snafu(transparent)] + Channel { source: ParseChannelError }, + + #[snafu(transparent)] + Edition { source: ParseEditionError }, + } + + impl From> for api::EvaluateResponse { + fn from(other: WithOutput) -> Self { + let WithOutput { response, stdout, stderr } = other; + + // The old playground didn't use Cargo, so it never had the + // Cargo output ("Compiling playground...") which is printed + // to stderr. Since this endpoint is used to inline results on + // the page, don't include the stderr unless an error + // occurred. + if response.success { + api::EvaluateResponse { result: stdout, error: None } + } else { + // When an error occurs, *some* consumers check for an + // `error` key, others assume that the error is crammed in + // the `result` field and then they string search for + // `error:` or `warning:`. Ew. We can put it in both. + let result = stderr + &stdout; + api::EvaluateResponse { result: result.clone(), error: Some(result) } + } + } + } + + impl TryFrom for CompileRequest { + type Error = ParseCompileRequestError; + + fn try_from(other: api::CompileRequest) -> Result { + let api::CompileRequest { + target, + assembly_flavor, + demangle_assembly, + process_assembly, + channel, + mode, + edition, + crate_type, + tests, + backtrace, + code, + } = other; + + Ok(Self { + target: parse_target( + &target, + assembly_flavor.as_deref(), + demangle_assembly.as_deref(), + process_assembly.as_deref(), + )?, + channel: parse_channel(&channel)?, + crate_type: parse_crate_type(&crate_type)?, + mode: parse_mode(&mode)?, + edition: parse_edition(&edition)?, + tests, + backtrace, + code, + }) + } + } + + #[derive(Debug, Snafu)] + pub(crate) enum ParseCompileRequestError { + #[snafu(transparent)] + Target { source: ParseCompileTargetError }, + + #[snafu(transparent)] + Channel { source: ParseChannelError }, + + #[snafu(transparent)] + CrateType { source: ParseCrateTypeError }, + + #[snafu(transparent)] + Mode { source: ParseModeError }, + + #[snafu(transparent)] + Edition { source: ParseEditionError }, + } + + impl From> for api::CompileResponse { + fn from(other: WithOutput) -> Self { + let WithOutput { response, stdout, stderr } = other; + let CompileResponse { success, exit_detail, code } = response; + + Self { success, exit_detail, code, stdout, stderr } + } + } + + impl TryFrom for ExecuteRequest { + type Error = ParseExecuteRequestError; + + fn try_from(other: api::ExecuteRequest) -> Result { + let api::ExecuteRequest { + channel, + mode, + edition, + crate_type, + tests, + backtrace, + code, + execution_tool, + } = other; + + Ok(Self { + channel: parse_channel(&channel)?, + crate_type: parse_crate_type(&crate_type)?, + mode: parse_mode(&mode)?, + edition: parse_edition(&edition)?, + tests, + backtrace, + code, + execution_tool: parse_execution_tool(&execution_tool)?, + }) + } + } + + #[derive(Debug, Snafu)] + pub(crate) enum ParseExecuteRequestError { + #[snafu(transparent)] + Channel { source: ParseChannelError }, + + #[snafu(transparent)] + CrateType { source: ParseCrateTypeError }, + + #[snafu(transparent)] + Mode { source: ParseModeError }, + + #[snafu(transparent)] + Edition { source: ParseEditionError }, + + #[snafu(transparent)] + ExecutionTool { source: ParseExecutionToolError }, + } + + impl From> for api::ExecuteResponse { + fn from(other: WithOutput) -> Self { + let WithOutput { response, stdout, stderr } = other; + let ExecuteResponse { success, exit_detail } = response; + + Self { success, exit_detail, stdout, stderr } + } + } + + impl TryFrom for FormatRequest { + type Error = ParseFormatRequestError; + + fn try_from(other: api::FormatRequest) -> std::result::Result { + let api::FormatRequest { channel, edition, code } = other; + + let channel = match channel { + Some(c) => parse_channel(&c)?, + None => Channel::Nightly, + }; + + Ok(FormatRequest { + channel, + crate_type: CrateType::Binary, // TODO: use what user has submitted + edition: parse_edition(&edition)?, + code, + }) + } + } + + #[derive(Debug, Snafu)] + pub(crate) enum ParseFormatRequestError { + #[snafu(transparent)] + Channel { source: ParseChannelError }, + + #[snafu(transparent)] + Edition { source: ParseEditionError }, + } + + impl From> for api::FormatResponse { + fn from(other: WithOutput) -> Self { + let WithOutput { response, stdout, stderr } = other; + let FormatResponse { success, exit_detail, code } = response; + + Self { success, exit_detail, code, stdout, stderr } + } + } + + impl TryFrom for ClippyRequest { + type Error = ParseClippyRequestError; + + fn try_from(other: api::ClippyRequest) -> std::result::Result { + let api::ClippyRequest { channel, crate_type, edition, code } = other; + + let channel = match channel { + Some(c) => parse_channel(&c)?, + None => Channel::Nightly, + }; + + Ok(ClippyRequest { + channel, + crate_type: parse_crate_type(&crate_type)?, + edition: parse_edition(&edition)?, + code, + }) + } + } + + #[derive(Debug, Snafu)] + pub(crate) enum ParseClippyRequestError { + #[snafu(transparent)] + Channel { source: ParseChannelError }, + + #[snafu(transparent)] + CrateType { source: ParseCrateTypeError }, + + #[snafu(transparent)] + Edition { source: ParseEditionError }, + } + + impl From> for api::ClippyResponse { + fn from(other: WithOutput) -> Self { + let WithOutput { response, stdout, stderr } = other; + let ClippyResponse { success, exit_detail } = response; + + Self { success, exit_detail, stdout, stderr } + } + } + + impl TryFrom for MiriRequest { + type Error = ParseMiriRequestError; + + fn try_from(other: api::MiriRequest) -> std::result::Result { + let api::MiriRequest { code, edition, tests, aliasing_model } = other; + + let aliasing_model = match aliasing_model { + Some(am) => parse_aliasing_model(&am)?, + None => AliasingModel::Stacked, + }; + + Ok(MiriRequest { + channel: Channel::Nightly, // TODO: use what user has submitted + crate_type: CrateType::Binary, // TODO: use what user has submitted + edition: parse_edition(&edition)?, + tests, + aliasing_model, + code, + }) + } + } + + #[derive(Debug, Snafu)] + pub(crate) enum ParseMiriRequestError { + #[snafu(transparent)] + Edition { source: ParseEditionError }, + #[snafu(transparent)] + AliasingMode { source: ParseAliasingModelError }, + } + + impl From> for api::MiriResponse { + fn from(other: WithOutput) -> Self { + let WithOutput { response, stdout, stderr } = other; + let MiriResponse { success, exit_detail } = response; + + Self { success, exit_detail, stdout, stderr } + } + } + + impl TryFrom for MacroExpansionRequest { + type Error = ParseMacroExpansionRequestError; + + fn try_from(other: api::MacroExpansionRequest) -> std::result::Result { + let api::MacroExpansionRequest { code, edition } = other; + + Ok(MacroExpansionRequest { + channel: Channel::Nightly, // TODO: use what user has submitted + crate_type: CrateType::Binary, // TODO: use what user has submitted + edition: parse_edition(&edition)?, + code, + }) + } + } + + #[derive(Debug, Snafu)] + pub(crate) enum ParseMacroExpansionRequestError { + #[snafu(transparent)] + Edition { source: ParseEditionError }, + } + + impl From> for api::MacroExpansionResponse { + fn from(other: WithOutput) -> Self { + let WithOutput { response, stdout, stderr } = other; + let MacroExpansionResponse { success, exit_detail } = response; + + Self { success, exit_detail, stdout, stderr } + } + } + + fn parse_target( + target: &str, + assembly_flavor: Option<&str>, + demangle_assembly: Option<&str>, + process_assembly: Option<&str>, + ) -> Result { + Ok(match target { + "asm" => { + let assembly_flavor = match assembly_flavor { + Some(f) => parse_assembly_flavor(f)?, + None => AssemblyFlavor::Att, + }; + + let demangle = match demangle_assembly { + Some(f) => parse_demangle_assembly(f)?, + None => DemangleAssembly::Demangle, + }; + + let process_assembly = match process_assembly { + Some(f) => parse_process_assembly(f)?, + None => ProcessAssembly::Filter, + }; + + CompileTarget::Assembly(assembly_flavor, demangle, process_assembly) + } + "llvm-ir" => CompileTarget::LlvmIr, + "mir" => CompileTarget::Mir, + "hir" => CompileTarget::Hir, + "wasm" => CompileTarget::Wasm, + value => return InvalidTargetSnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + pub(crate) enum ParseCompileTargetError { + #[snafu(transparent)] + AssemblyFlavor { source: ParseAssemblyFlavorError }, + + #[snafu(transparent)] + DemangleAssembly { source: ParseDemangleAssemblyError }, + + #[snafu(transparent)] + ProcessAssembly { source: ParseProcessAssemblyError }, + + #[snafu(display("'{value}' is not a valid target"))] + InvalidTarget { value: String }, + } + + fn parse_assembly_flavor(s: &str) -> Result { + Ok(match s { + "att" => AssemblyFlavor::Att, + "intel" => AssemblyFlavor::Intel, + value => return ParseAssemblyFlavorSnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid assembly flavor"))] + pub(crate) struct ParseAssemblyFlavorError { + value: String, + } + + fn parse_demangle_assembly(s: &str) -> Result { + Ok(match s { + "demangle" => DemangleAssembly::Demangle, + "mangle" => DemangleAssembly::Mangle, + value => return ParseDemangleAssemblySnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid demangle option"))] + pub(crate) struct ParseDemangleAssemblyError { + value: String, + } + + fn parse_process_assembly(s: &str) -> Result { + Ok(match s { + "filter" => ProcessAssembly::Filter, + "raw" => ProcessAssembly::Raw, + value => return ParseProcessAssemblySnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid assembly processing option"))] + pub(crate) struct ParseProcessAssemblyError { + value: String, + } + + pub(crate) fn parse_channel(s: &str) -> Result { + Ok(match s { + "stable" => Channel::Stable, + "beta" => Channel::Beta, + "nightly" => Channel::Nightly, + value => return ParseChannelSnafu { value }.fail(), + }) + } + + pub(crate) fn parse_execution_tool(s: &str) -> Result { + Ok(match s { + "cargo" => ExecutionTool::Cargo, + "anneal-verify" => ExecutionTool::AnnealVerify, + value => return ParseExecutionToolSnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid execution tool"))] + pub(crate) struct ParseExecutionToolError { + value: String, + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid channel"))] + pub(crate) struct ParseChannelError { + value: String, + } + + pub(crate) fn parse_crate_type(s: &str) -> Result { + use {CrateType::*, LibraryType::*}; + + Ok(match s { + "bin" => Binary, + "lib" => Library(Lib), + "dylib" => Library(Dylib), + "rlib" => Library(Rlib), + "staticlib" => Library(Staticlib), + "cdylib" => Library(Cdylib), + "proc-macro" => Library(ProcMacro), + value => return ParseCrateTypeSnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid crate type"))] + pub(crate) struct ParseCrateTypeError { + value: String, + } + + pub(crate) fn parse_mode(s: &str) -> Result { + Ok(match s { + "debug" => Mode::Debug, + "release" => Mode::Release, + value => return ParseModeSnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid mode"))] + pub(crate) struct ParseModeError { + value: String, + } + + pub(crate) fn parse_edition(s: &str) -> Result { + Ok(match s { + "2015" => Edition::Rust2015, + "2018" => Edition::Rust2018, + "2021" => Edition::Rust2021, + "2024" => Edition::Rust2024, + value => return ParseEditionSnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid edition"))] + pub(crate) struct ParseEditionError { + value: String, + } + + pub(crate) fn parse_aliasing_model(s: &str) -> Result { + Ok(match s { + "stacked" => AliasingModel::Stacked, + "tree" => AliasingModel::Tree, + value => return ParseAliasingModelSnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid aliasing model"))] + pub(crate) struct ParseAliasingModelError { + value: String, + } + + impl From for api::MetaGistResponse { + fn from(me: gist::Gist) -> Self { + api::MetaGistResponse { id: me.id, url: me.url, code: me.code } + } + } +} diff --git a/anneal/v1/playground/rust-anneal-playground/ui/src/server_axum/cache.rs b/anneal/v1/playground/rust-anneal-playground/ui/src/server_axum/cache.rs new file mode 100644 index 0000000000..0000595116 --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/src/server_axum/cache.rs @@ -0,0 +1,254 @@ +use futures::{ + future::{Fuse, FusedFuture as _}, + FutureExt as _, +}; +use orchestrator::{DropErrorDetailsExt as _, TaskAbortExt as _}; +use snafu::prelude::*; +use std::{ + future::Future, + pin::pin, + sync::Arc, + time::{Duration, Instant, SystemTime}, +}; +use tokio::{ + select, + sync::{mpsc, oneshot}, + time, +}; +use tokio_util::task::AbortOnDropHandle; +use tracing::warn; + +const ONE_HUNDRED_MILLISECONDS: Duration = Duration::from_millis(100); +const TEN_SECONDS: Duration = Duration::from_secs(10); +const TEN_MINUTES: Duration = Duration::from_secs(10 * 60); + +pub const SANDBOX_CACHE_TIME_TO_LIVE: Duration = TEN_MINUTES; +const SANDBOX_CACHE_ERROR_TIME_TO_LIVE: Duration = TEN_SECONDS; +const SANDBOX_CACHE_INITIAL_DELAY: Duration = ONE_HUNDRED_MILLISECONDS; + +pub type Stamped = (T, SystemTime); + +#[derive(Debug)] +pub struct CacheTx(mpsc::Sender>) +where + E: snafu::Error + 'static; + +impl Clone for CacheTx +where + E: snafu::Error + 'static, +{ + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl CacheTx +where + E: snafu::Error + 'static, +{ + pub fn spawn( + f: impl FnOnce(mpsc::Receiver>) -> Fut, + ) -> (AbortOnDropHandle<()>, Self) + where + Fut: Future + Send + 'static, + { + let (tx, rx) = mpsc::channel(8); + let task = tokio::spawn(f(rx)).abort_on_drop(); + let cache_tx = CacheTx(tx); + (task, cache_tx) + } + + pub async fn get(&self) -> Result, CacheTxError> { + use cache_tx_error::*; + + let (tx, rx) = oneshot::channel(); + self.0.send(tx).await.drop_error_details().context(SendToTaskSnafu)?; + let value = rx.await.context(RecvFromTaskSnafu)?; + Ok((value.0?, value.1)) + } +} + +#[derive(Debug, Snafu)] +#[snafu(module)] +pub enum CacheTxError { + #[snafu(display("Could not contact the cache"))] + SendToTask { source: mpsc::error::SendError<()> }, + + #[snafu(display("Did not receive a response from the cache"))] + RecvFromTask { source: oneshot::error::RecvError }, + + #[snafu(transparent)] + Inner { source: CacheError }, +} + +pub type CacheTaskItem = oneshot::Sender>; +pub type CacheResponse = Stamped>>; + +pub async fn cache_task(mut rx: mpsc::Receiver>, generator: G) +where + T: Clone + PartialEq, + T: Send + 'static, + E: snafu::Error, + E: Send + Sync, + G: FnMut() -> Fut, + G: Clone + Send + 'static, + Fut: Future>, + Fut: Send, +{ + let mut cached_value = CacheInfo::build(Err(CacheError::Empty)); + // Sleep is fused so that it resolves exactly once + let mut cache_expired = pin!(time::sleep(SANDBOX_CACHE_INITIAL_DELAY).fuse()); + let mut new_value = pin!(Fuse::terminated()); + + loop { + enum Event + where + E: snafu::Error + 'static, + { + Rx(Option>), + + Expired, + + New(Result>>, tokio::task::JoinError>), + } + use Event::*; + + let event = select! { + rx = rx.recv() => Rx(rx), + + _ = &mut cache_expired => Expired, + + new = &mut new_value => New(new), + }; + + match event { + // All senders dropped, indicating shutdown + Rx(None) => break, + + // Someone wants the cached value + Rx(Some(resp_tx)) => { + resp_tx.send(cached_value.stamped_value()).ok(/* Don't care if they received it */); + } + + // The cached value has expired, start working on a new one + Expired => { + assert!(new_value.is_terminated(), "The previous cache task has not completed",); + + let new_value_task = tokio::spawn({ + let mut generator = generator.clone(); + + async move { + let new_value = generator().await.map_err(CacheError::from); + CacheInfo::build(new_value) + } + }) + .abort_on_drop(); + + new_value.set(new_value_task.fuse()); + } + + // A new value is ready to be stored in the cache + New(new_value) => { + assert!( + cache_expired.is_terminated(), + "The previous cache timer has not completed", + ); + + let new_value = new_value.unwrap_or_else(|e| { + warn!(?e, "The cache task exited abnormally"); + CacheInfo::build(Err(CacheError::Empty)) + }); + + cached_value = new_value.try_combine_with_previous(cached_value); + + let cache_ttl = if cached_value.value.is_ok() { + SANDBOX_CACHE_TIME_TO_LIVE + } else { + SANDBOX_CACHE_ERROR_TIME_TO_LIVE + }; + + cache_expired.set(time::sleep(cache_ttl).fuse()); + } + } + } +} + +#[derive(Debug)] +struct CacheInfo { + value: T, + creation_time: SystemTime, + validation_time: Instant, +} + +impl CacheInfo { + fn build(value: T) -> Self { + let creation_time = SystemTime::now(); + let validation_time = Instant::now(); + + Self { value, creation_time, validation_time } + } + + fn stamped_value(&self) -> Stamped + where + T: Clone, + { + (self.value.clone(), self.creation_time) + } +} + +impl CacheInfo> { + fn try_combine_with_previous(self, mut old_value: Self) -> Self + where + T: PartialEq, + { + match (&old_value.value, &self.value) { + // Always take the successful value + (Err(_), Ok(_)) => self, + + // It doesn't really matter which error we keep, so pick the newer one + (Err(_), Err(_)) => self, + + // Keep the stale version instead of the broken version + (Ok(_), Err(_)) => old_value, + + (Ok(old), Ok(new)) => { + if old == new { + // The value hasn't changed; record that we have + // checked recently, but keep the creation time to + // preserve caching. + old_value.validation_time = self.validation_time; + old_value + } else { + self + } + } + } + } +} + +#[derive(Debug, Snafu)] +pub enum CacheError +where + E: snafu::Error + 'static, +{ + #[snafu(display("No value has been cached yet"))] + Empty, + + #[snafu(transparent)] + Real { + #[snafu(source(from(E, Arc::new)))] + source: Arc, + }, +} + +impl Clone for CacheError +where + E: snafu::Error + 'static, +{ + fn clone(&self) -> Self { + match self { + CacheError::Empty => CacheError::Empty, + CacheError::Real { source } => CacheError::Real { source: source.clone() }, + } + } +} diff --git a/anneal/v1/playground/rust-anneal-playground/ui/src/server_axum/websocket.rs b/anneal/v1/playground/rust-anneal-playground/ui/src/server_axum/websocket.rs new file mode 100644 index 0000000000..a9b1c0611e --- /dev/null +++ b/anneal/v1/playground/rust-anneal-playground/ui/src/server_axum/websocket.rs @@ -0,0 +1,951 @@ +use crate::public_http_api::default_execution_tool; +use crate::{ + metrics::{self, record_metric, Endpoint, HasLabelsCore, Outcome}, + request_database::Handle, + server_axum::{api_orchestrator_integration_impls::*, AnnealPrewarm}, + WebSocketConfig, +}; + +use axum::extract::ws::{Message, WebSocket}; +use futures::{future::Fuse, Future, FutureExt, StreamExt, TryFutureExt}; +use orchestrator::{ + coordinator::{self, Coordinator, CoordinatorFactory, DockerBackend}, + DropErrorDetailsExt, +}; +use snafu::prelude::*; +use std::{ + collections::BTreeMap, + convert::TryFrom, + pin::pin, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, + time::{Duration, Instant}, +}; +use tokio::{ + sync::{mpsc, Semaphore}, + task::{AbortHandle, JoinSet}, + time, +}; +use tokio_util::{ + sync::{CancellationToken, DropGuard}, + time::FutureExt as _, +}; +use tracing::{error, info, instrument, warn, Instrument}; + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct MetaInner { + sequence_number: i64, +} + +type Meta = Arc; + +#[derive(serde::Deserialize)] +#[serde(tag = "type")] +enum HandshakeMessage { + #[serde(rename = "websocket/connected")] + Connected { + payload: Connected, + #[allow(unused)] + meta: Meta, + }, +} + +#[derive(serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct Connected { + i_accept_this_is_an_unsupported_api: bool, +} + +#[derive(serde::Deserialize)] +#[serde(tag = "type")] +enum WSMessageRequest { + #[serde(rename = "output/execute/wsExecuteRequest")] + ExecuteRequest { payload: ExecuteRequest, meta: Meta }, + + #[serde(rename = "output/execute/wsExecuteStdin")] + ExecuteStdin { payload: String, meta: Meta }, + + #[serde(rename = "output/execute/wsExecuteStdinClose")] + ExecuteStdinClose { meta: Meta }, + + #[serde(rename = "output/execute/wsExecuteKill")] + ExecuteKill { meta: Meta }, +} + +#[derive(serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct ExecuteRequest { + channel: String, + mode: String, + edition: String, + crate_type: String, + tests: bool, + code: String, + backtrace: bool, + #[serde(default = "default_execution_tool")] + execution_tool: String, +} + +impl TryFrom for coordinator::ExecuteRequest { + type Error = ExecuteRequestParseError; + + fn try_from(value: ExecuteRequest) -> Result { + let ExecuteRequest { + channel, + mode, + edition, + crate_type, + tests, + code, + backtrace, + execution_tool, + } = value; + + Ok(coordinator::ExecuteRequest { + channel: parse_channel(&channel)?, + mode: parse_mode(&mode)?, + edition: parse_edition(&edition)?, + crate_type: parse_crate_type(&crate_type)?, + tests, + backtrace, + code, + execution_tool: parse_execution_tool(&execution_tool)?, + }) + } +} + +#[derive(Debug, Snafu)] +pub(crate) enum ExecuteRequestParseError { + #[snafu(transparent)] + Channel { source: ParseChannelError }, + + #[snafu(transparent)] + CrateType { source: ParseCrateTypeError }, + + #[snafu(transparent)] + Mode { source: ParseModeError }, + + #[snafu(transparent)] + Edition { source: ParseEditionError }, + + #[snafu(transparent)] + ExecutionTool { source: ParseExecutionToolError }, +} + +#[derive(Debug, serde::Serialize)] +#[serde(tag = "type")] +enum MessageResponse { + #[serde(rename = "websocket/error")] + Error { payload: WSError, meta: Meta }, + + #[serde(rename = "featureFlags")] + FeatureFlags { payload: FeatureFlags, meta: Meta }, + + #[serde(rename = "output/execute/wsExecuteBegin")] + ExecuteBegin { meta: Meta }, + + #[serde(rename = "output/execute/wsExecuteStdout")] + ExecuteStdout { payload: String, meta: Meta }, + + #[serde(rename = "output/execute/wsExecuteStderr")] + ExecuteStderr { payload: String, meta: Meta }, + + #[serde(rename = "output/execute/wsExecuteStatus")] + ExecuteStatus { payload: ExecuteStatus, meta: Meta }, + + #[serde(rename = "output/execute/wsExecuteEnd")] + ExecuteEnd { payload: ExecuteResponse, meta: Meta }, +} + +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct WSError { + error: String, +} + +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct FeatureFlags {} + +impl From for FeatureFlags { + fn from(_value: crate::FeatureFlags) -> Self { + Self {} + } +} + +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ExecuteStatus { + resident_set_size_bytes: u64, + total_time_secs: f64, +} + +impl From for ExecuteStatus { + fn from(value: orchestrator::coordinator::ExecuteStatus) -> Self { + let coordinator::ExecuteStatus { resident_set_size_bytes, total_time_secs } = value; + + Self { resident_set_size_bytes, total_time_secs } + } +} + +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct ExecuteResponse { + success: bool, + exit_detail: String, +} + +#[instrument(skip_all, fields(ws_id))] +pub(crate) async fn handle( + socket: WebSocket, + config: WebSocketConfig, + factory: Arc, + feature_flags: FeatureFlags, + anneal_prewarm: AnnealPrewarm, + db: Handle, +) { + struct MetricGuard { + clean: bool, + start: Instant, + } + + impl MetricGuard { + fn new() -> Self { + static WEBSOCKET_ID: AtomicU64 = AtomicU64::new(0); + + metrics::LIVE_WS.inc(); + let start = Instant::now(); + + let id = WEBSOCKET_ID.fetch_add(1, Ordering::SeqCst); + tracing::Span::current().record("ws_id", id); + info!("WebSocket started"); + + Self { clean: false, start } + } + } + + impl Drop for MetricGuard { + fn drop(&mut self) { + info!(clean = self.clean, "WebSocket ending"); + metrics::LIVE_WS.dec(); + let elapsed = self.start.elapsed(); + metrics::DURATION_WS.observe(elapsed.as_secs_f64()); + } + } + + let mut mg = MetricGuard::new(); + + let hc = handle_core(socket, config, factory, feature_flags, anneal_prewarm, db) + .timeout(config.overall_timeout()) + .await; + + if hc.is_err() { + error!("`handle_core` exceeded the overall timeout"); + } + + mg.clean = true; +} + +type TaggedError = (Error, Option); +type ResponseTx = mpsc::Sender>; +type SharedCoordinator = Arc>; + +/// Manages a limited amount of access to the `Coordinator`. +/// +/// Has a number of responsibilities: +/// +/// - Constructs the `Coordinator` on demand. +/// +/// - Only allows one job of a certain kind at a time (e.g. executing +/// vs formatting). Older jobs will be cancelled. +/// +/// - Allows limited parallelism between jobs of different types. +struct CoordinatorManager { + coordinator: SharedCoordinator, + tasks: JoinSet>, + semaphore: Arc, + abort_handles: [Option; Self::N_KINDS], +} + +impl CoordinatorManager { + const N_PARALLEL: usize = 2; + + const N_KINDS: usize = 1; + const KIND_EXECUTE: usize = 0; + + fn new(factory: &CoordinatorFactory, prewarmed: Option>) -> Self { + if prewarmed.is_some() { + info!("[anneal-prewarm] using prewarmed stable coordinator for WebSocket"); + } + + Self { + coordinator: Arc::new(prewarmed.unwrap_or_else(|| factory.build())), + tasks: Default::default(), + semaphore: Arc::new(Semaphore::new(Self::N_PARALLEL)), + abort_handles: Default::default(), + } + } + + fn is_empty(&self) -> bool { + self.tasks.is_empty() + } + + async fn join_next( + &mut self, + ) -> Option, tokio::task::JoinError>> { + self.tasks.join_next().await + } + + async fn spawn(&mut self, handler: F) -> CoordinatorManagerResult<()> + where + F: FnOnce(SharedCoordinator) -> Fut, + F: 'static + Send, + Fut: Future>, + Fut: 'static + Send, + { + let coordinator = self.coordinator.clone(); + let semaphore = self.semaphore.clone(); + + let new_abort_handle = self.tasks.spawn( + async move { + let _permit = semaphore.acquire().await; + handler(coordinator).await + } + .in_current_span(), + ); + + let kind = Self::KIND_EXECUTE; // TODO: parameterize when we get a second kind + let old_abort_handle = self.abort_handles[kind].replace(new_abort_handle); + + if let Some(abort_handle) = old_abort_handle { + abort_handle.abort(); + } + + Ok(()) + } + + async fn idle(&mut self) -> CoordinatorManagerResult<()> { + use coordinator_manager_error::*; + + Arc::get_mut(&mut self.coordinator) + .context(OutstandingCoordinatorIdleSnafu)? + .idle() + .await + .context(IdleSnafu)?; + + Ok(()) + } + + async fn shutdown(mut self) -> CoordinatorManagerResult<()> { + use coordinator_manager_error::*; + + self.tasks.shutdown().await; + Arc::into_inner(self.coordinator) + .context(OutstandingCoordinatorShutdownSnafu)? + .shutdown() + .await + .context(ShutdownSnafu)?; + + Ok(()) + } +} + +#[derive(Debug, Snafu)] +#[snafu(module)] +pub enum CoordinatorManagerError { + #[snafu(display("The coordinator is still referenced and cannot be idled"))] + OutstandingCoordinatorIdle, + + #[snafu(display("Could not idle the coordinator"))] + Idle { source: coordinator::Error }, + + #[snafu(display("The coordinator is still referenced and cannot be shut down"))] + OutstandingCoordinatorShutdown, + + #[snafu(display("Could not shut down the coordinator"))] + Shutdown { source: coordinator::Error }, +} + +type CoordinatorManagerResult = std::result::Result; + +async fn handle_core( + mut socket: WebSocket, + config: WebSocketConfig, + factory: Arc, + feature_flags: FeatureFlags, + anneal_prewarm: AnnealPrewarm, + db: Handle, +) { + let accepted_handshake = + connect_handshake(&mut socket).timeout(config.handshake_timeout).await.unwrap_or(false); + + if !accepted_handshake { + return; + } + + let (tx, mut rx) = mpsc::channel(3); + + let ff = MessageResponse::FeatureFlags { payload: feature_flags, meta: create_server_meta() }; + + if tx.send(Ok(ff)).await.is_err() { + return; + } + + let prewarmed = anneal_prewarm.take().await; + let mut manager = CoordinatorManager::new(&factory, prewarmed); + let mut session_timeout = pin!(time::sleep(config.session_timeout)); + let mut idle_timeout = pin!(Fuse::terminated()); + + let mut active_executions = BTreeMap::new(); + let mut active_execution_gc_interval = time::interval(Duration::from_secs(30)); + + loop { + use Event::*; + + enum Event { + Request(Option>), + Response(Option>), + Task(Result, tokio::task::JoinError>), + GarbageCollection, + IdleTimeout, + IdleRequest, + SessionTimeout, + } + + let event = tokio::select! { + request = socket.recv() => Request(request), + + resp = rx.recv() => Response(resp), + + // We don't care if there are no running tasks + Some(task) = manager.join_next() => Task(task), + + _ = active_execution_gc_interval.tick() => GarbageCollection, + + _ = &mut idle_timeout, if manager.is_empty() => IdleTimeout, + + _ = factory.container_requested(), if manager.is_empty() => IdleRequest, + + _ = &mut session_timeout => SessionTimeout, + }; + + match event { + Request(request) => { + metrics::WS_INCOMING.inc(); + + match request { + // browser disconnected + None => break, + + Some(Ok(Message::Text(txt))) => { + handle_msg(&txt, &tx, &mut manager, &mut active_executions, &db).await + } + + // unknown message type + Some(Ok(_)) => continue, + + Some(Err(e)) => super::record_websocket_error(e.to_string()), + } + } + + Response(resp) => { + let resp = resp.expect("The rx should never close as we have a tx"); + + let success = resp.is_ok(); + let resp = resp.unwrap_or_else(error_to_response); + let resp = response_to_message(resp); + + if socket.send(resp).await.is_err() { + // We can't send a response + break; + } + + let success = if success { "true" } else { "false" }; + metrics::WS_OUTGOING.with_label_values(&[success]).inc(); + } + + Task(task) => { + // The last task has completed which means we are a + // candidate for idling in a little while. + if manager.is_empty() { + idle_timeout.set(time::sleep(config.idle_timeout).fuse()); + } + + let (error, meta) = match task { + Ok(Ok(())) => continue, + + Ok(Err(error)) => error, + + Err(error) => { + // The task was cancelled; no need to report + let Ok(panic) = error.try_into_panic() else { + continue; + }; + + let text = match panic.downcast::() { + Ok(text) => *text, + Err(panic) => match panic.downcast::<&str>() { + Ok(text) => text.to_string(), + _ => "An unknown panic occurred".into(), + }, + }; + (WebSocketTaskPanicSnafu { text }.build(), None) + } + }; + + if tx.send(Err((error, meta))).await.is_err() { + // We can't send a response + break; + } + } + + GarbageCollection => { + active_executions + .retain(|_id, (_, tx)| tx.as_ref().is_some_and(|tx| !tx.is_closed())); + } + + IdleTimeout | IdleRequest => { + if matches!(event, IdleRequest) { + info!("Container requested to idle"); + } + + match manager.idle().timeout(config.idle_shutdown_timeout).await { + Ok(idled) => { + let idled = idled.context(StreamingCoordinatorIdleSnafu); + let Err(error) = idled else { continue }; + + if tx.send(Err((error, None))).await.is_err() { + // We can't send a response + break; + } + } + + Err(_) => { + error!("Timed out idling the Coordinator"); + break; + } + } + } + + SessionTimeout => break, + } + } + + drop((tx, rx, socket)); + let shutdown = manager.shutdown().timeout(config.shutdown_timeout).await; + + match shutdown { + Ok(Ok(())) => {} + Ok(Err(e)) => error!("Could not shut down the Coordinator: {e:?}"), + Err(_) => error!("Timed out shutting down the Coordinator"), + } +} + +async fn connect_handshake(socket: &mut WebSocket) -> bool { + let Some(Ok(Message::Text(txt))) = socket.recv().await else { + return false; + }; + let Ok(HandshakeMessage::Connected { payload, .. }) = serde_json::from_str(&txt) else { + return false; + }; + if !payload.i_accept_this_is_an_unsupported_api { + return false; + } + socket.send(Message::Text(txt)).await.is_ok() +} + +fn create_server_meta() -> Meta { + Arc::new(MetaInner { sequence_number: -1 }) +} + +fn error_to_response((error, meta): TaggedError) -> MessageResponse { + let error = snafu::CleanedErrorText::new(&error) + .map(|(_, t, _)| t) + .reduce(|e, t| e + ": " + &t) + .unwrap_or_default(); + let payload = WSError { error }; + + let meta = meta.unwrap_or_else(create_server_meta); + + MessageResponse::Error { payload, meta } +} + +fn response_to_message(response: MessageResponse) -> Message { + const LAST_CHANCE_ERROR: &str = + r#"{ "type": "WEBSOCKET_ERROR", "error": "Unable to serialize JSON" }"#; + let resp = serde_json::to_string(&response).unwrap_or_else(|_| LAST_CHANCE_ERROR.into()); + Message::Text(resp.into()) +} + +type ActiveExecutionInfo = (DropGuard, Option>); + +async fn handle_msg( + txt: &str, + tx: &ResponseTx, + manager: &mut CoordinatorManager, + active_executions: &mut BTreeMap, + db: &Handle, +) { + use WSMessageRequest::*; + + let message_type = serde_json::from_str::(txt) + .ok() + .and_then(|v| v.get("type").and_then(|t| t.as_str()).map(str::to_owned)); + info!( + message_type = message_type.as_deref().unwrap_or(""), + message_len = txt.len(), + "websocket message received" + ); + + let msg = serde_json::from_str(txt).context(DeserializationSnafu); + + match msg { + Ok(ExecuteRequest { payload, meta }) => { + let token = CancellationToken::new(); + let (execution_tx, execution_rx) = mpsc::channel(8); + + let guard = db.clone().start_with_guard("ws.Execute", txt).await; + + active_executions + .insert(meta.sequence_number, (token.clone().drop_guard(), Some(execution_tx))); + + // TODO: Should a single execute / build / etc. session have a timeout of some kind? + let spawned = manager + .spawn({ + let tx = tx.clone(); + let meta = meta.clone(); + async |coordinator| { + let r = handle_execute( + token, + execution_rx, + tx, + coordinator, + payload, + meta.clone(), + ) + .context(StreamingExecuteSnafu) + .map_err(|e| (e, Some(meta))) + .await; + + guard.complete_now(r) + } + }) + .await + .context(StreamingCoordinatorSpawnSnafu); + + if let Err(e) = spawned { + tx.send(Err((e, Some(meta)))).await.ok(/* We don't care if the channel is closed */); + } + } + + Ok(ExecuteStdin { payload, meta }) => { + let Some((_, Some(execution_tx))) = active_executions.get(&meta.sequence_number) else { + warn!("Received stdin for an execution that is no longer active"); + return; + }; + let sent = execution_tx + .send(payload) + .await + .drop_error_details() + .context(StreamingCoordinatorExecuteStdinSnafu); + + if let Err(e) = sent { + tx.send(Err((e, Some(meta)))).await.ok(/* We don't care if the channel is closed */); + } + } + + Ok(ExecuteStdinClose { meta }) => { + let Some((_, execution_tx)) = active_executions.get_mut(&meta.sequence_number) else { + warn!("Received stdin close for an execution that is no longer active"); + return; + }; + + *execution_tx = None; // Drop to signal closed + } + + Ok(ExecuteKill { meta }) => { + let Some((token, _)) = active_executions.remove(&meta.sequence_number) else { + warn!("Received kill for an execution that is no longer active"); + return; + }; + drop(token); + } + + Err(e) => { + warn!( + message_type = message_type.as_deref().unwrap_or(""), + error = %e, + "websocket message parse failed" + ); + tx.send(Err((e, None))).await.ok(/* We don't care if the channel is closed */); + } + } +} + +#[derive(Debug)] +enum CompletedOrAbandoned { + Abandoned, + Completed(T), +} + +macro_rules! abandon_if_closed { + ($sent:expr) => { + if $sent.is_err() { + return Ok(CompletedOrAbandoned::Abandoned); + } + }; +} + +async fn handle_execute( + token: CancellationToken, + rx: mpsc::Receiver, + tx: ResponseTx, + coordinator: SharedCoordinator, + req: ExecuteRequest, + meta: Meta, +) -> ExecuteResult<()> { + use execute_error::*; + use CompletedOrAbandoned::*; + + let req = coordinator::ExecuteRequest::try_from(req).context(BadRequestSnafu)?; + let is_anneal_verify = req.execution_tool == coordinator::ExecutionTool::AnnealVerify; + if is_anneal_verify { + info!( + sequence_number = meta.sequence_number, + channel = ?req.channel, + mode = ?req.mode, + edition = ?req.edition, + "anneal verify websocket request received" + ); + } + + let labels_core = req.labels_core(); + let sequence_number = meta.sequence_number; + + let start = Instant::now(); + let v = handle_execute_inner(token, rx, tx, coordinator, req, meta).await; + let elapsed = start.elapsed(); + + let outcome = match &v { + Ok(Abandoned) => Outcome::Abandoned, + Ok(Completed(v)) => *v, + Err(_) => Outcome::ErrorServer, + }; + + record_metric(Endpoint::Execute, labels_core, outcome, elapsed); + if is_anneal_verify { + info!( + sequence_number, + elapsed_ms = elapsed.as_millis(), + outcome = ?outcome, + "anneal verify websocket request finished" + ); + } + + v?; + Ok(()) +} + +async fn handle_execute_inner( + token: CancellationToken, + mut rx: mpsc::Receiver, + tx: ResponseTx, + coordinator: SharedCoordinator, + req: coordinator::ExecuteRequest, + meta: Meta, +) -> ExecuteResult> { + use execute_error::*; + use CompletedOrAbandoned::*; + + let is_anneal_verify = req.execution_tool == coordinator::ExecutionTool::AnnealVerify; + let anneal_request_start = Instant::now(); + + let coordinator::ActiveExecution { + permit: _permit, + mut task, + stdin_tx, + mut stdout_rx, + mut stderr_rx, + mut status_rx, + } = coordinator.begin_execute(token, req.clone()).await.context(BeginSnafu)?; + + let sent = tx.send(Ok(MessageResponse::ExecuteBegin { meta: meta.clone() })).await; + abandon_if_closed!(sent); + + let mut stdin_tx = Some(stdin_tx); + + let send_stdout = async |payload| { + let meta = meta.clone(); + tx.send(Ok(MessageResponse::ExecuteStdout { payload, meta })).await + }; + + let send_stderr = async |payload| { + let meta = meta.clone(); + tx.send(Ok(MessageResponse::ExecuteStderr { payload, meta })).await + }; + + let mut reported = false; + let mut stdout_cache = String::new(); + let mut stderr_cache = String::new(); + + let status = loop { + enum Event { + Stdin(Option), + Stdout(String), + Stderr(String), + Status(coordinator::ExecuteStatus), + } + use Event::*; + + let event = tokio::select! { + response = &mut task => break response, + + stdin = rx.recv(), if stdin_tx.is_some() => Stdin(stdin), + + Some(stdout) = stdout_rx.recv() => Stdout(stdout), + + Some(stderr) = stderr_rx.recv() => Stderr(stderr), + + Some(status) = status_rx.next() => Status(status) + }; + + match event { + Stdin(Some(stdin)) => { + stdin_tx + .as_ref() + .unwrap(/* This is a precondition */) + .send(stdin) + .await + .drop_error_details() + .context(StdinSnafu)?; + } + Stdin(None) => { + let stdin_tx = stdin_tx.take(); + drop(stdin_tx); // Signal closed + } + + Stdout(stdout) => { + stdout_cache.push_str(&stdout); + let sent = send_stdout(stdout).await; + abandon_if_closed!(sent); + } + + Stderr(stderr) => { + stderr_cache.push_str(&stderr); + let sent = send_stderr(stderr).await; + abandon_if_closed!(sent); + } + + Status(status) => { + if !reported && status.total_time_secs > 60.0 { + error!("Request consumed more than 60s of CPU time: {req:?}"); + reported = true; + } + + let payload = status.into(); + let meta = meta.clone(); + let sent = tx.send(Ok(MessageResponse::ExecuteStatus { payload, meta })).await; + abandon_if_closed!(sent); + } + } + }; + + // Drain any remaining output + while let Some(Some(stdout)) = stdout_rx.recv().now_or_never() { + stdout_cache.push_str(&stdout); + let sent = send_stdout(stdout).await; + abandon_if_closed!(sent); + } + + while let Some(Some(stderr)) = stderr_rx.recv().now_or_never() { + stderr_cache.push_str(&stderr); + let sent = send_stderr(stderr).await; + abandon_if_closed!(sent); + } + + let status = status.context(EndSnafu)?; + let outcome = Outcome::from_success(&status); + + let coordinator::ExecuteResponse { success, exit_detail } = status; + + if is_anneal_verify { + let backend_total_ms = anneal_request_start.elapsed().as_millis(); + let cargo_anneal_verify_ms = extract_cargo_anneal_verify_ms(&stdout_cache) + .or_else(|| extract_cargo_anneal_verify_ms(&stderr_cache)); + let cargo_anneal_verify_ms_found = cargo_anneal_verify_ms.is_some(); + let backend_overhead_ms = + cargo_anneal_verify_ms.map(|ms| backend_total_ms.saturating_sub(ms.into())); + + info!( + backend_total_ms, + cargo_anneal_verify_ms = cargo_anneal_verify_ms.unwrap_or(0), + cargo_anneal_verify_ms_found, + backend_overhead_ms = backend_overhead_ms.unwrap_or(0), + success, + "[anneal-verify-timing] event=finish" + ); + } + + let sent = tx + .send(Ok(MessageResponse::ExecuteEnd { + payload: ExecuteResponse { success, exit_detail }, + meta, + })) + .await; + abandon_if_closed!(sent); + + Ok(Completed(outcome)) +} + +fn extract_cargo_anneal_verify_ms(output: &str) -> Option { + output + .lines() + .find(|line| { + line.contains("[anneal] verification succeeded in") + || line.contains("[anneal] verification failed after") + }) + .and_then(|line| line.split_whitespace().rev().nth(1)) + .and_then(|ms| ms.parse().ok()) +} + +#[derive(Debug, Snafu)] +#[snafu(module)] +pub(crate) enum ExecuteError { + #[snafu(display("The request could not be parsed"))] + BadRequest { source: ExecuteRequestParseError }, + + #[snafu(display("Could not begin the execution session"))] + Begin { source: coordinator::ExecuteError }, + + #[snafu(display("Could not end the execution session"))] + End { source: coordinator::ExecuteError }, + + #[snafu(display("Could not send stdin to the coordinator"))] + Stdin { source: tokio::sync::mpsc::error::SendError<()> }, +} + +type ExecuteResult = std::result::Result; + +#[derive(Debug, Snafu)] +enum Error { + #[snafu(display("Unable to deserialize request"))] + Deserialization { source: serde_json::Error }, + + #[snafu(display("The WebSocket worker panicked: {}", text))] + WebSocketTaskPanic { text: String }, + + #[snafu(display("Unable to spawn a coordinator task"))] + StreamingCoordinatorSpawn { source: CoordinatorManagerError }, + + #[snafu(display("Unable to idle the coordinator"))] + StreamingCoordinatorIdle { source: CoordinatorManagerError }, + + #[snafu(display("Unable to perform a streaming execute"))] + StreamingExecute { source: ExecuteError }, + + #[snafu(display("Unable to pass stdin to the active execution"))] + StreamingCoordinatorExecuteStdin { source: tokio::sync::mpsc::error::SendError<()> }, +}