From eee148325617f7433b5beae33885766d6180647b Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 2 Dec 2024 14:15:46 +0100 Subject: [PATCH 01/55] streaming-spike: dummy impl of streaming receiver --- Cargo.lock | 859 +++++++++++++++++++++++++++++++-------------- server/Cargo.toml | 1 + server/src/main.rs | 29 ++ 3 files changed, 623 insertions(+), 266 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fa18906..6f4efc0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "actix-codec" @@ -106,7 +106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -260,7 +260,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" @@ -338,9 +338,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -353,49 +353,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.91" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] @@ -414,7 +414,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -463,9 +463,9 @@ dependencies = [ [[package]] name = "aws-config" -version = "1.5.8" +version = "1.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7198e6f03240fdceba36656d8be440297b6b82270325908c7381f37d826a74f6" +checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" dependencies = [ "aws-credential-types", "aws-runtime", @@ -531,11 +531,10 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.57.0" +version = "1.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8888c238bf93c77c5df8274b3999fd7fc1bb3fb658616f40dfde9e4fcd9efd94" +checksum = "35fe5e7f71b1cc6274e905d3bcc7daf94099ac2d4cba83447ffb959b5b27b3c1" dependencies = [ - "ahash", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -566,9 +565,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.46.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc2faec3205d496c7e57eff685dd944203df7ce16a4116d0281c44021788a7b" +checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -588,9 +587,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.47.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c93c241f52bc5e0476e259c953234dab7e2a35ee207ee202e86c0095ec4951dc" +checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" dependencies = [ "aws-credential-types", "aws-runtime", @@ -610,9 +609,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.46.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b259429be94a3459fa1b00c5684faee118d74f9577cc50aebadc36e507c63b5f" +checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" dependencies = [ "aws-credential-types", "aws-runtime", @@ -633,9 +632,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8db6904450bafe7473c6ca9123f88cc11089e41a025408f992db4e22d3be68" +checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -673,9 +672,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.60.12" +version = "0.60.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23" +checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -745,9 +744,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a065c0fe6fdbdf9f11817eb68582b2ab4aff9e9c39e986ae48f7ec576c6322db" +checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -772,9 +771,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.7.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96" +checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -789,9 +788,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.7" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147100a7bea70fa20ef224a6bad700358305f5dc0f84649c53769761395b355b" +checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" dependencies = [ "base64-simd", "bytes", @@ -921,14 +920,14 @@ dependencies = [ "home", "http 1.1.0", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-named-pipe", "hyper-rustls 0.27.3", "hyper-util", "hyperlocal", "log", "pin-project-lite", - "rustls 0.23.15", + "rustls 0.23.19", "rustls-native-certs 0.7.3", "rustls-pemfile 2.2.0", "rustls-pki-types", @@ -937,7 +936,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-util", "tower-service", @@ -991,9 +990,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bytes-utils" @@ -1007,9 +1006,9 @@ dependencies = [ [[package]] name = "bytestring" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" dependencies = [ "bytes", ] @@ -1025,9 +1024,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.30" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "jobserver", "libc", @@ -1040,6 +1039,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -1101,14 +1106,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "clone_dyn_types" @@ -1118,9 +1123,9 @@ checksum = "0c23e9c07455288757e5142aaeb0e999f9f34dd2d3c27c9b5f8e3f85f4cb1eda" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" @@ -1212,9 +1217,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -1302,7 +1307,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1313,7 +1318,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1352,13 +1357,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1379,7 +1384,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1389,7 +1394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1402,7 +1407,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1424,7 +1429,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1478,9 +1483,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -1516,12 +1521,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1535,11 +1540,27 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "eventsource-client" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ddc25e1ad2cc0106d5e2d967397b4fb2068a66677ee9b0eea4600e5cfe8fb4" +dependencies = [ + "futures", + "hyper 0.14.31", + "hyper-rustls 0.24.2", + "hyper-timeout", + "log", + "pin-project", + "rand", + "tokio", +] + [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "ff" @@ -1565,9 +1586,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1665,7 +1686,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1769,7 +1790,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -1778,9 +1799,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -1788,7 +1809,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -1809,9 +1830,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -1954,14 +1975,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -1980,7 +2001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "pin-project-lite", "tokio", @@ -2012,9 +2033,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.15", + "rustls 0.23.19", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -2022,6 +2043,18 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.31", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -2030,7 +2063,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -2040,16 +2073,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.5.1", "pin-project-lite", "socket2", "tokio", @@ -2065,7 +2098,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "pin-project-lite", "tokio", @@ -2095,6 +2128,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2103,19 +2254,30 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "impl-more" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e658178c10c747241199382079c0f195ce229866fbf4aa0d46fa6107fe33d2ec" +checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" [[package]] name = "indexmap" @@ -2130,12 +2292,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "serde", ] @@ -2196,9 +2358,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -2211,10 +2373,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -2232,9 +2395,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libgit2-sys" @@ -2277,6 +2440,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "local-channel" version = "0.1.5" @@ -2322,7 +2491,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] @@ -2383,11 +2552,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", "log", "wasi", @@ -2497,9 +2665,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.67" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b8cefcf97f41316955f9294cd61f639bdcfa9f2f230faac6cb896aa8ab64704" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -2518,7 +2686,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -2550,7 +2718,7 @@ dependencies = [ "js-sys", "once_cell", "pin-project-lite", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2589,7 +2757,7 @@ dependencies = [ "rand", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2660,7 +2828,7 @@ dependencies = [ "regex", "regex-syntax 0.8.5", "structmeta", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -2682,7 +2850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] @@ -2706,7 +2874,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -2720,11 +2888,31 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-project" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -2765,9 +2953,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2809,7 +2997,7 @@ dependencies = [ "parking_lot", "procfs", "protobuf", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2857,7 +3045,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -2868,45 +3056,49 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "quinn" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.15", + "rustls 0.23.19", "socket2", - "thiserror", + "thiserror 2.0.3", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", + "getrandom", "rand", "ring", "rustc-hash", - "rustls 0.23.15", + "rustls 0.23.19", + "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.3", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" dependencies = [ + "cfg_aliases", "libc", "once_cell", "socket2", @@ -2970,7 +3162,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rand", - "rustls 0.23.15", + "rustls 0.23.19", "rustls-native-certs 0.7.3", "rustls-pemfile 2.2.0", "rustls-pki-types", @@ -3003,13 +3195,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -3024,9 +3216,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -3053,20 +3245,20 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", @@ -3079,7 +3271,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.15", + "rustls 0.23.19", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -3145,7 +3337,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.82", + "syn 2.0.90", "walkdir", ] @@ -3167,9 +3359,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc_version" @@ -3182,9 +3374,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -3207,9 +3399,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.15" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", "once_cell", @@ -3268,6 +3460,9 @@ name = "rustls-pki-types" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -3307,9 +3502,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -3359,9 +3554,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -3375,29 +3570,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.213" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.213" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -3415,7 +3610,7 @@ dependencies = [ "futures", "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -3427,7 +3622,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -3452,7 +3647,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_derive", "serde_json", @@ -3469,7 +3664,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -3502,9 +3697,9 @@ dependencies = [ [[package]] name = "shadow-rs" -version = "0.35.1" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2311e39772c00391875f40e34d43efef247b23930143a70ca5fbec9505937420" +checksum = "f1b2328fb3ec0d5302f95915e7e77cfc2ff943714d9970bc4b66e9eacf318687" dependencies = [ "const_format", "git2", @@ -3576,9 +3771,9 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3600,6 +3795,12 @@ dependencies = [ "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "str-buf" version = "3.0.3" @@ -3621,7 +3822,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -3632,7 +3833,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -3654,9 +3855,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.82" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -3665,13 +3866,24 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "system-configuration" version = "0.6.1" @@ -3695,9 +3907,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -3724,7 +3936,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -3735,7 +3947,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", "test-case-core", ] @@ -3760,7 +3972,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-tar", @@ -3770,31 +3982,51 @@ dependencies = [ [[package]] name = "testcontainers-modules" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708fce58200e480633a428b7356fc39eb7fef02e47bd6faa94ba1d0746e6f17e" +checksum = "064a2677e164cad39ef3c1abddb044d5a25c49d27005804563d8c4227aac8bd0" dependencies = [ "testcontainers", ] [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -3840,6 +4072,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -3874,6 +4116,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.4.0" @@ -3882,7 +4134,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -3911,7 +4163,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.15", + "rustls 0.23.19", "rustls-pki-types", "tokio", ] @@ -3963,9 +4215,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -3975,20 +4227,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -4007,9 +4259,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -4017,9 +4269,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -4054,7 +4306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -4117,33 +4369,15 @@ dependencies = [ [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" @@ -4180,6 +4414,7 @@ dependencies = [ "clap-markdown", "dashmap", "env_logger", + "eventsource-client", "futures", "futures-core", "iter_tools", @@ -4197,7 +4432,7 @@ dependencies = [ "rand", "redis", "reqwest", - "rustls 0.23.15", + "rustls 0.23.19", "rustls-pemfile 2.2.0", "rustls-pki-types", "semver", @@ -4236,9 +4471,9 @@ dependencies = [ [[package]] name = "unleash-yggdrasil" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c3e96d03aa2efa8deac6205bb7ca7f1912e0dfdc6a8edb63aa4df1c9eaa8012" +checksum = "c654b3a246b3a77e537499674e791ba9d94250957c1c192302e063589e9ded9f" dependencies = [ "chrono", "convert_case 0.6.0", @@ -4264,9 +4499,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -4280,6 +4515,18 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -4288,11 +4535,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" -version = "5.1.1" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8861811f7213bb866cd02319acb69a15b0ef8ca46874e805bd92d488c779036a" +checksum = "514a48569e4e21c86d0b84b5612b5e73c0b2cf09db63260134ba426d4e8ea714" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_json", "utoipa-gen", @@ -4300,21 +4547,21 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.1.1" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fadf94f07d67df4b15e6490dd9a9d59d7374849413e7f137eafe52fdcbd0db5" +checksum = "5629efe65599d0ccd5d493688cbf6e03aa7c1da07fe59ff97cf5977ed0637f66" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] name = "utoipa-swagger-ui" -version = "8.0.2" +version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054c946332dfc6e4bc31f704aa714fcbed73e9f412dd939f9ed86869e6304c3" +checksum = "a5c80b4dd79ea382e8374d67dcce22b5c6663fa13a82ad3886441d1bbede5e35" dependencies = [ "actix-web", "mime_guess", @@ -4384,9 +4631,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -4395,36 +4642,37 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4432,28 +4680,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", @@ -4471,9 +4719,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -4696,6 +4944,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "xattr" version = "1.3.1" @@ -4719,6 +4979,30 @@ version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -4737,7 +5021,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", ] [[package]] @@ -4746,20 +5051,42 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "zip" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" dependencies = [ "arbitrary", "crc32fast", "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.6.0", + "indexmap 2.7.0", "memchr", - "thiserror", + "thiserror 2.0.3", "zopfli", ] diff --git a/server/Cargo.toml b/server/Cargo.toml index 1844b7d9..deb9c8b4 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -40,6 +40,7 @@ cidr = "0.3.0" clap = { version = "4.5.19", features = ["derive", "env"] } clap-markdown = "0.1.4" dashmap = "6.0.1" +eventsource-client = "0.13.0" futures = "0.3.30" futures-core = "0.3.30" iter_tools = "0.24.0" diff --git a/server/src/main.rs b/server/src/main.rs index 85307cc8..598ebfdb 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,6 +7,7 @@ use actix_web::{web, App, HttpServer}; use clap::Parser; use dashmap::DashMap; use futures::future::join_all; +use futures::{Stream, TryStreamExt}; use unleash_types::client_features::ClientFeatures; use unleash_types::client_metrics::ConnectVia; use utoipa::OpenApi; @@ -25,6 +26,23 @@ use unleash_edge::{cli, client_api, frontend_api, health_checker, openapi, ready use unleash_edge::{edge_api, prom_metrics}; use unleash_edge::{internal_backstage, tls}; +fn tail_events(client: impl eventsource_client::Client) -> impl Stream> { + client + .stream() + .map_ok(|event| match event { + eventsource_client::SSE::Connected(connection) => { + println!("got connected: \nstatus={}", connection.response().status()) + } + eventsource_client::SSE::Event(ev) => { + println!("got an event: {}\n{}", ev.event_type, ev.data) + } + eventsource_client::SSE::Comment(comment) => { + println!("got a comment: \n{}", comment) + } + }) + .map_err(|err| eprintln!("error streaming events: {:?}", err)) +} + #[cfg(not(tarpaulin_include))] #[actix_web::main] async fn main() -> Result<(), anyhow::Error> { @@ -147,6 +165,17 @@ async fn main() -> Result<(), anyhow::Error> { .shutdown_timeout(5) .client_request_timeout(std::time::Duration::from_secs(request_timeout)); + let es_client = eventsource_client::ClientBuilder::for_url("http://localhost:4242/streaming")? + .header( + "authorization", + "pmi2:development.2879322577f497c358ec706976ae3dd0311112fa0f146a20f8050d79", + )? + .build(); + + let mut stream = tail_events(es_client); + + tokio::spawn(async move { while let Some(_) = stream.try_next().await.unwrap() {} }); + match schedule_args.mode { cli::EdgeMode::Edge(edge) => { let refresher = feature_refresher.clone().unwrap(); From 8ac5e4f6a23852fcc6a1fe34c741494f02a6fe58 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 2 Dec 2024 14:18:14 +0100 Subject: [PATCH 02/55] simpler cargo update --- Cargo.lock | 800 ++++++++++++++++++----------------------------------- 1 file changed, 266 insertions(+), 534 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f4efc0f..3915d7cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,7 +106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -260,7 +260,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.21" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -338,9 +338,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -353,49 +353,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" dependencies = [ "derive_arbitrary", ] @@ -414,7 +414,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -463,9 +463,9 @@ dependencies = [ [[package]] name = "aws-config" -version = "1.5.10" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" +checksum = "7198e6f03240fdceba36656d8be440297b6b82270325908c7381f37d826a74f6" dependencies = [ "aws-credential-types", "aws-runtime", @@ -531,10 +531,11 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.64.0" +version = "1.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fe5e7f71b1cc6274e905d3bcc7daf94099ac2d4cba83447ffb959b5b27b3c1" +checksum = "8888c238bf93c77c5df8274b3999fd7fc1bb3fb658616f40dfde9e4fcd9efd94" dependencies = [ + "ahash", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -565,9 +566,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.49.0" +version = "1.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" +checksum = "0dc2faec3205d496c7e57eff685dd944203df7ce16a4116d0281c44021788a7b" dependencies = [ "aws-credential-types", "aws-runtime", @@ -587,9 +588,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.50.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" +checksum = "c93c241f52bc5e0476e259c953234dab7e2a35ee207ee202e86c0095ec4951dc" dependencies = [ "aws-credential-types", "aws-runtime", @@ -609,9 +610,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.50.0" +version = "1.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" +checksum = "b259429be94a3459fa1b00c5684faee118d74f9577cc50aebadc36e507c63b5f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -632,9 +633,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.5" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" +checksum = "cc8db6904450bafe7473c6ca9123f88cc11089e41a025408f992db4e22d3be68" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -672,9 +673,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.60.13" +version = "0.60.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" +checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -744,9 +745,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.3" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" +checksum = "a065c0fe6fdbdf9f11817eb68582b2ab4aff9e9c39e986ae48f7ec576c6322db" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -771,9 +772,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.7.3" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -788,9 +789,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.9" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" +checksum = "147100a7bea70fa20ef224a6bad700358305f5dc0f84649c53769761395b355b" dependencies = [ "base64-simd", "bytes", @@ -920,14 +921,14 @@ dependencies = [ "home", "http 1.1.0", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.0", "hyper-named-pipe", "hyper-rustls 0.27.3", "hyper-util", "hyperlocal", "log", "pin-project-lite", - "rustls 0.23.19", + "rustls 0.23.15", "rustls-native-certs 0.7.3", "rustls-pemfile 2.2.0", "rustls-pki-types", @@ -936,7 +937,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-util", "tower-service", @@ -990,9 +991,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bytes-utils" @@ -1006,9 +1007,9 @@ dependencies = [ [[package]] name = "bytestring" -version = "1.4.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" dependencies = [ "bytes", ] @@ -1024,9 +1025,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" dependencies = [ "jobserver", "libc", @@ -1039,12 +1040,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chrono" version = "0.4.38" @@ -1106,14 +1101,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clone_dyn_types" @@ -1123,9 +1118,9 @@ checksum = "0c23e9c07455288757e5142aaeb0e999f9f34dd2d3c27c9b5f8e3f85f4cb1eda" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "combine" @@ -1217,9 +1212,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -1307,7 +1302,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -1318,7 +1313,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -1357,13 +1352,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -1384,7 +1379,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -1394,7 +1389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -1407,7 +1402,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -1429,7 +1424,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -1483,9 +1478,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -1521,12 +1516,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1558,9 +1553,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "ff" @@ -1586,9 +1581,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -1686,7 +1681,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -1790,7 +1785,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1799,9 +1794,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.7" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -1809,7 +1804,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.7.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1830,9 +1825,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" dependencies = [ "allocator-api2", "equivalent", @@ -1975,14 +1970,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.7", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -2001,7 +1996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.5.1", + "hyper 1.5.0", "hyper-util", "pin-project-lite", "tokio", @@ -2033,9 +2028,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.5.1", + "hyper 1.5.0", "hyper-util", - "rustls 0.23.19", + "rustls 0.23.15", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -2063,7 +2058,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.0", "hyper-util", "native-tls", "tokio", @@ -2073,16 +2068,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.1", + "hyper 1.5.0", "pin-project-lite", "socket2", "tokio", @@ -2098,7 +2093,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.0", "hyper-util", "pin-project-lite", "tokio", @@ -2128,124 +2123,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -2254,30 +2131,19 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", + "unicode-bidi", + "unicode-normalization", ] [[package]] name = "impl-more" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" +checksum = "e658178c10c747241199382079c0f195ce229866fbf4aa0d46fa6107fe33d2ec" [[package]] name = "indexmap" @@ -2292,12 +2158,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.0", "serde", ] @@ -2358,9 +2224,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" @@ -2373,11 +2239,10 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ - "once_cell", "wasm-bindgen", ] @@ -2395,9 +2260,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libgit2-sys" @@ -2440,12 +2305,6 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - [[package]] name = "local-channel" version = "0.1.5" @@ -2491,7 +2350,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.0", ] [[package]] @@ -2552,10 +2411,11 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "log", "wasi", @@ -2665,9 +2525,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "7b8cefcf97f41316955f9294cd61f639bdcfa9f2f230faac6cb896aa8ab64704" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -2686,7 +2546,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -2718,7 +2578,7 @@ dependencies = [ "js-sys", "once_cell", "pin-project-lite", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -2757,7 +2617,7 @@ dependencies = [ "rand", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -2828,7 +2688,7 @@ dependencies = [ "regex", "regex-syntax 0.8.5", "structmeta", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -2850,7 +2710,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror", "ucd-trie", ] @@ -2874,7 +2734,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -2905,14 +2765,14 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2953,9 +2813,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -2997,7 +2857,7 @@ dependencies = [ "parking_lot", "procfs", "protobuf", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -3045,7 +2905,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -3056,49 +2916,45 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.19", + "rustls 0.23.15", "socket2", - "thiserror 2.0.3", + "thiserror", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", - "getrandom", "rand", "ring", "rustc-hash", - "rustls 0.23.19", - "rustls-pki-types", + "rustls 0.23.15", "slab", - "thiserror 2.0.3", + "thiserror", "tinyvec", "tracing", - "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ - "cfg_aliases", "libc", "once_cell", "socket2", @@ -3162,7 +3018,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rand", - "rustls 0.23.19", + "rustls 0.23.15", "rustls-native-certs 0.7.3", "rustls-pemfile 2.2.0", "rustls-pki-types", @@ -3195,13 +3051,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] @@ -3216,9 +3072,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -3245,20 +3101,20 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2 0.4.7", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.0", "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", @@ -3271,7 +3127,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.19", + "rustls 0.23.15", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -3337,7 +3193,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.90", + "syn 2.0.82", "walkdir", ] @@ -3359,9 +3215,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustc_version" @@ -3374,9 +3230,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -3399,9 +3255,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" dependencies = [ "log", "once_cell", @@ -3460,9 +3316,6 @@ name = "rustls-pki-types" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" -dependencies = [ - "web-time", -] [[package]] name = "rustls-webpki" @@ -3502,9 +3355,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -3554,9 +3407,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -3570,29 +3423,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -3610,7 +3463,7 @@ dependencies = [ "futures", "percent-encoding", "serde", - "thiserror 1.0.69", + "thiserror", "tracing", ] @@ -3622,7 +3475,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -3647,7 +3500,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -3664,7 +3517,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -3697,9 +3550,9 @@ dependencies = [ [[package]] name = "shadow-rs" -version = "0.35.2" +version = "0.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b2328fb3ec0d5302f95915e7e77cfc2ff943714d9970bc4b66e9eacf318687" +checksum = "2311e39772c00391875f40e34d43efef247b23930143a70ca5fbec9505937420" dependencies = [ "const_format", "git2", @@ -3771,9 +3624,9 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3795,12 +3648,6 @@ dependencies = [ "der", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "str-buf" version = "3.0.3" @@ -3822,7 +3669,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -3833,7 +3680,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -3855,9 +3702,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", @@ -3866,24 +3713,13 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" dependencies = [ "futures-core", ] -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "system-configuration" version = "0.6.1" @@ -3907,9 +3743,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -3936,7 +3772,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -3947,7 +3783,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", "test-case-core", ] @@ -3972,7 +3808,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", "tokio-tar", @@ -3982,51 +3818,31 @@ dependencies = [ [[package]] name = "testcontainers-modules" -version = "0.11.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064a2677e164cad39ef3c1abddb044d5a25c49d27005804563d8c4227aac8bd0" +checksum = "708fce58200e480633a428b7356fc39eb7fef02e47bd6faa94ba1d0746e6f17e" dependencies = [ "testcontainers", ] [[package]] name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" -dependencies = [ - "thiserror-impl 2.0.3", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -4072,16 +3888,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" version = "1.8.0" @@ -4134,7 +3940,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -4163,7 +3969,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.19", + "rustls 0.23.15", "rustls-pki-types", "tokio", ] @@ -4215,9 +4021,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", @@ -4227,20 +4033,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -4259,9 +4065,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.2.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ "serde", "tracing-core", @@ -4269,9 +4075,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -4306,7 +4112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] @@ -4369,15 +4175,33 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.0" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] [[package]] name = "unicode-segmentation" @@ -4432,7 +4256,7 @@ dependencies = [ "rand", "redis", "reqwest", - "rustls 0.23.19", + "rustls 0.23.15", "rustls-pemfile 2.2.0", "rustls-pki-types", "semver", @@ -4471,9 +4295,9 @@ dependencies = [ [[package]] name = "unleash-yggdrasil" -version = "0.14.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c654b3a246b3a77e537499674e791ba9d94250957c1c192302e063589e9ded9f" +checksum = "6c3e96d03aa2efa8deac6205bb7ca7f1912e0dfdc6a8edb63aa4df1c9eaa8012" dependencies = [ "chrono", "convert_case 0.6.0", @@ -4499,9 +4323,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -4515,18 +4339,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -4535,11 +4347,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" -version = "5.2.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514a48569e4e21c86d0b84b5612b5e73c0b2cf09db63260134ba426d4e8ea714" +checksum = "8861811f7213bb866cd02319acb69a15b0ef8ca46874e805bd92d488c779036a" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.6.0", "serde", "serde_json", "utoipa-gen", @@ -4547,21 +4359,21 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.2.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5629efe65599d0ccd5d493688cbf6e03aa7c1da07fe59ff97cf5977ed0637f66" +checksum = "5fadf94f07d67df4b15e6490dd9a9d59d7374849413e7f137eafe52fdcbd0db5" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.90", + "syn 2.0.82", ] [[package]] name = "utoipa-swagger-ui" -version = "8.0.3" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c80b4dd79ea382e8374d67dcce22b5c6663fa13a82ad3886441d1bbede5e35" +checksum = "1054c946332dfc6e4bc31f704aa714fcbed73e9f412dd939f9ed86869e6304c3" dependencies = [ "actix-web", "mime_guess", @@ -4631,9 +4443,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -4642,37 +4454,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4680,28 +4491,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.82", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -4719,9 +4530,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -4944,18 +4755,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "xattr" version = "1.3.1" @@ -4979,30 +4778,6 @@ version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", - "synstructure", -] - [[package]] name = "zerocopy" version = "0.7.35" @@ -5021,28 +4796,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", - "synstructure", + "syn 2.0.82", ] [[package]] @@ -5051,42 +4805,20 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "zip" -version = "2.2.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" +checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" dependencies = [ "arbitrary", "crc32fast", "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.7.0", + "indexmap 2.6.0", "memchr", - "thiserror 2.0.3", + "thiserror", "zopfli", ] From 644debde2b68e869ba0736602519660016cbeaf9 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 2 Dec 2024 14:19:32 +0100 Subject: [PATCH 03/55] streaming-spike: remove full token --- server/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index 598ebfdb..8584889f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -166,10 +166,7 @@ async fn main() -> Result<(), anyhow::Error> { .client_request_timeout(std::time::Duration::from_secs(request_timeout)); let es_client = eventsource_client::ClientBuilder::for_url("http://localhost:4242/streaming")? - .header( - "authorization", - "pmi2:development.2879322577f497c358ec706976ae3dd0311112fa0f146a20f8050d79", - )? + .header("authorization", "some-token")? .build(); let mut stream = tail_events(es_client); From fc63a1603e777d1677c9c86a2b29c6642923d25a Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 2 Dec 2024 14:25:49 +0100 Subject: [PATCH 04/55] streaming-spike: satisfy clippy --- server/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main.rs b/server/src/main.rs index 8584889f..3e05682b 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -171,7 +171,7 @@ async fn main() -> Result<(), anyhow::Error> { let mut stream = tail_events(es_client); - tokio::spawn(async move { while let Some(_) = stream.try_next().await.unwrap() {} }); + tokio::spawn(async move { while stream.try_next().await.unwrap().is_some() {} }); match schedule_args.mode { cli::EdgeMode::Edge(edge) => { From a69eeacbbca7a2ea40076d69cf419bb4d7908190 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 3 Dec 2024 10:54:02 +0100 Subject: [PATCH 05/55] wip: set up streaming per token --- server/src/http/feature_refresher.rs | 60 ++++++++++++++++++++++++++++ server/src/main.rs | 26 ++++++++---- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index e62d7d38..3c7a3d95 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -4,6 +4,8 @@ use std::{sync::Arc, time::Duration}; use actix_web::http::header::EntityTag; use chrono::Utc; use dashmap::DashMap; +use eventsource_client::Client; +use futures::{Stream, TryStreamExt}; use reqwest::StatusCode; use tracing::{debug, info, warn}; use unleash_types::client_features::Segment; @@ -299,6 +301,64 @@ impl FeatureRefresher { } } + pub async fn start_streaming_features_background_task(&self) { + let refreshes = self.get_tokens_due_for_refresh(); + for refresh in refreshes { + let token = refresh.token.clone(); + // let client = self.unleash_client.clone(); + // let base_url = client.base_url.clone(); + let streaming_url = "http://localhost:4242/api/client/features/stream"; + + let es_client = match eventsource_client::ClientBuilder::for_url(&streaming_url) { + Ok(builder) => match builder.header("Authorization", &token.token) { + Ok(builder) => builder.build(), + Err(e) => { + warn!("Failed to add authorization header for streaming: {}", e); + continue; + } + }, + Err(e) => { + warn!("Failed to create streaming client: {}", e); + continue; + } + }; + + let refresher = self.clone(); + tokio::spawn(async move { + let mut stream = es_client + .stream() + .map_ok(move |event| { + let token = token.clone(); + let refresher = refresher.clone(); + async move { + match event { + eventsource_client::SSE::Event(_) => { + debug!("Got SSE event, refreshing features"); + refresher + .refresh_single(TokenRefresh::new(token, None)) + .await; + } + eventsource_client::SSE::Connected(_) => { + debug!("SSE Connected, performing initial refresh"); + refresher + .refresh_single(TokenRefresh::new(token, None)) + .await; + } + _ => { + debug!("Got a different SSE event {:#?}", event); + } + } + } + }) + .map_err(|e| warn!("Error in SSE stream: {:?}", e)); + + while let Ok(Some(handler)) = stream.try_next().await { + handler.await; + } + }); + } + } + pub async fn start_refresh_features_background_task(&self) { loop { tokio::select! { diff --git a/server/src/main.rs b/server/src/main.rs index 3e05682b..1728485f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -165,17 +165,24 @@ async fn main() -> Result<(), anyhow::Error> { .shutdown_timeout(5) .client_request_timeout(std::time::Duration::from_secs(request_timeout)); - let es_client = eventsource_client::ClientBuilder::for_url("http://localhost:4242/streaming")? - .header("authorization", "some-token")? - .build(); + // let es_client = eventsource_client::ClientBuilder::for_url("http://localhost:4242/streaming")? + // .header("authorization", "some-token")? + // .build(); - let mut stream = tail_events(es_client); + // let mut stream = tail_events(es_client); - tokio::spawn(async move { while stream.try_next().await.unwrap().is_some() {} }); + // tokio::spawn(async move { while stream.try_next().await.unwrap().is_some() {} }); match schedule_args.mode { cli::EdgeMode::Edge(edge) => { + let refresher_for_background = feature_refresher.clone().unwrap(); + tokio::spawn(async move { + refresher_for_background + .start_refresh_features_background_task() + .await; + }); let refresher = feature_refresher.clone().unwrap(); + let validator = token_validator_schedule.clone().unwrap(); tokio::select! { _ = server.run() => { @@ -183,9 +190,12 @@ async fn main() -> Result<(), anyhow::Error> { clean_shutdown(persistence.clone(), lazy_feature_cache.clone(), lazy_token_cache.clone(), metrics_cache_clone.clone(), feature_refresher.clone()).await; tracing::info!("Actix was shutdown properly"); }, - _ = refresher.start_refresh_features_background_task() => { - tracing::info!("Feature refresher unexpectedly shut down"); - } + // _ = refresher.start_streaming_features_background_task() => { + // tracing::info!("Streaming feature refresher unexpectedly shut down"); + // } + // _ = refresher.start_refresh_features_background_task() => { + // tracing::info!("Feature refresher unexpectedly shut down"); + // } _ = unleash_edge::http::background_send_metrics::send_metrics_task(metrics_cache_clone.clone(), refresher.clone(), edge.metrics_interval_seconds.try_into().unwrap()) => { tracing::info!("Metrics poster unexpectedly shut down"); } From 25a0227307569e5fabe5010f9f8336dfab9037fd Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 3 Dec 2024 12:25:15 +0100 Subject: [PATCH 06/55] streaming-spike: disable polling, only stream --- server/src/http/feature_refresher.rs | 20 ++++++++++---------- server/src/main.rs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 3c7a3d95..512dfdf6 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -307,7 +307,7 @@ impl FeatureRefresher { let token = refresh.token.clone(); // let client = self.unleash_client.clone(); // let base_url = client.base_url.clone(); - let streaming_url = "http://localhost:4242/api/client/features/stream"; + let streaming_url = "http://localhost:4242/api/client/streaming"; let es_client = match eventsource_client::ClientBuilder::for_url(&streaming_url) { Ok(builder) => match builder.header("Authorization", &token.token) { @@ -359,15 +359,15 @@ impl FeatureRefresher { } } - pub async fn start_refresh_features_background_task(&self) { - loop { - tokio::select! { - _ = tokio::time::sleep(Duration::from_secs(5)) => { - self.refresh_features().await; - } - } - } - } + // pub async fn start_refresh_features_background_task(&self) { + // loop { + // tokio::select! { + // _ = tokio::time::sleep(Duration::from_secs(5)) => { + // self.refresh_features().await; + // } + // } + // } + // } pub async fn hydrate_new_tokens(&self) { let hydrations = self.get_tokens_never_refreshed(); diff --git a/server/src/main.rs b/server/src/main.rs index 1728485f..f546b929 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -178,7 +178,7 @@ async fn main() -> Result<(), anyhow::Error> { let refresher_for_background = feature_refresher.clone().unwrap(); tokio::spawn(async move { refresher_for_background - .start_refresh_features_background_task() + .start_streaming_features_background_task() .await; }); let refresher = feature_refresher.clone().unwrap(); From 4822888a9662a4d9338a6a4b4daa2025f8ac3c42 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 3 Dec 2024 12:41:40 +0100 Subject: [PATCH 07/55] streaming-spike: use url from client --- server/src/http/feature_refresher.rs | 23 +++++++++++------------ server/src/main.rs | 4 +--- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 512dfdf6..9ba74b96 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -305,9 +305,8 @@ impl FeatureRefresher { let refreshes = self.get_tokens_due_for_refresh(); for refresh in refreshes { let token = refresh.token.clone(); - // let client = self.unleash_client.clone(); - // let base_url = client.base_url.clone(); - let streaming_url = "http://localhost:4242/api/client/streaming"; + println!("{}/client/streaming", self.unleash_client.urls.api_url); + let streaming_url = format!("{}/client/streaming", self.unleash_client.urls.api_url); let es_client = match eventsource_client::ClientBuilder::for_url(&streaming_url) { Ok(builder) => match builder.header("Authorization", &token.token) { @@ -359,15 +358,15 @@ impl FeatureRefresher { } } - // pub async fn start_refresh_features_background_task(&self) { - // loop { - // tokio::select! { - // _ = tokio::time::sleep(Duration::from_secs(5)) => { - // self.refresh_features().await; - // } - // } - // } - // } + pub async fn start_refresh_features_background_task(&self) { + loop { + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(5)) => { + self.refresh_features().await; + } + } + } + } pub async fn hydrate_new_tokens(&self) { let hydrations = self.get_tokens_never_refreshed(); diff --git a/server/src/main.rs b/server/src/main.rs index f546b929..07671c51 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -176,6 +176,7 @@ async fn main() -> Result<(), anyhow::Error> { match schedule_args.mode { cli::EdgeMode::Edge(edge) => { let refresher_for_background = feature_refresher.clone().unwrap(); + tokio::spawn(async move { refresher_for_background .start_streaming_features_background_task() @@ -190,9 +191,6 @@ async fn main() -> Result<(), anyhow::Error> { clean_shutdown(persistence.clone(), lazy_feature_cache.clone(), lazy_token_cache.clone(), metrics_cache_clone.clone(), feature_refresher.clone()).await; tracing::info!("Actix was shutdown properly"); }, - // _ = refresher.start_streaming_features_background_task() => { - // tracing::info!("Streaming feature refresher unexpectedly shut down"); - // } // _ = refresher.start_refresh_features_background_task() => { // tracing::info!("Feature refresher unexpectedly shut down"); // } From dd43a4d935c95da79256822003931b9338c74dd7 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 3 Dec 2024 12:44:18 +0100 Subject: [PATCH 08/55] streaming-spike: minor cleanup --- server/src/http/feature_refresher.rs | 3 +-- server/src/main.rs | 26 -------------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 9ba74b96..75b5220b 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -5,7 +5,7 @@ use actix_web::http::header::EntityTag; use chrono::Utc; use dashmap::DashMap; use eventsource_client::Client; -use futures::{Stream, TryStreamExt}; +use futures::TryStreamExt; use reqwest::StatusCode; use tracing::{debug, info, warn}; use unleash_types::client_features::Segment; @@ -305,7 +305,6 @@ impl FeatureRefresher { let refreshes = self.get_tokens_due_for_refresh(); for refresh in refreshes { let token = refresh.token.clone(); - println!("{}/client/streaming", self.unleash_client.urls.api_url); let streaming_url = format!("{}/client/streaming", self.unleash_client.urls.api_url); let es_client = match eventsource_client::ClientBuilder::for_url(&streaming_url) { diff --git a/server/src/main.rs b/server/src/main.rs index 07671c51..3e16bc65 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,7 +7,6 @@ use actix_web::{web, App, HttpServer}; use clap::Parser; use dashmap::DashMap; use futures::future::join_all; -use futures::{Stream, TryStreamExt}; use unleash_types::client_features::ClientFeatures; use unleash_types::client_metrics::ConnectVia; use utoipa::OpenApi; @@ -26,23 +25,6 @@ use unleash_edge::{cli, client_api, frontend_api, health_checker, openapi, ready use unleash_edge::{edge_api, prom_metrics}; use unleash_edge::{internal_backstage, tls}; -fn tail_events(client: impl eventsource_client::Client) -> impl Stream> { - client - .stream() - .map_ok(|event| match event { - eventsource_client::SSE::Connected(connection) => { - println!("got connected: \nstatus={}", connection.response().status()) - } - eventsource_client::SSE::Event(ev) => { - println!("got an event: {}\n{}", ev.event_type, ev.data) - } - eventsource_client::SSE::Comment(comment) => { - println!("got a comment: \n{}", comment) - } - }) - .map_err(|err| eprintln!("error streaming events: {:?}", err)) -} - #[cfg(not(tarpaulin_include))] #[actix_web::main] async fn main() -> Result<(), anyhow::Error> { @@ -165,14 +147,6 @@ async fn main() -> Result<(), anyhow::Error> { .shutdown_timeout(5) .client_request_timeout(std::time::Duration::from_secs(request_timeout)); - // let es_client = eventsource_client::ClientBuilder::for_url("http://localhost:4242/streaming")? - // .header("authorization", "some-token")? - // .build(); - - // let mut stream = tail_events(es_client); - - // tokio::spawn(async move { while stream.try_next().await.unwrap().is_some() {} }); - match schedule_args.mode { cli::EdgeMode::Edge(edge) => { let refresher_for_background = feature_refresher.clone().unwrap(); From 38e09fe0eb715b4c5b9debb7948b463d2b6f4aad Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 3 Dec 2024 13:59:15 +0100 Subject: [PATCH 09/55] streaming-spike: add retries --- server/src/http/feature_refresher.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 75b5220b..19a26825 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -309,7 +309,16 @@ impl FeatureRefresher { let es_client = match eventsource_client::ClientBuilder::for_url(&streaming_url) { Ok(builder) => match builder.header("Authorization", &token.token) { - Ok(builder) => builder.build(), + Ok(builder) => builder + .reconnect( + eventsource_client::ReconnectOptions::reconnect(true) + .retry_initial(true) + .delay(Duration::from_secs(5)) + .delay_max(Duration::from_secs(30)) + .backoff_factor(2) + .build(), + ) + .build(), Err(e) => { warn!("Failed to add authorization header for streaming: {}", e); continue; From a63e53107bc6bead84035fd1833fb5c69226aa22 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 4 Dec 2024 09:56:25 +0100 Subject: [PATCH 10/55] streaming-spike: add /client/streaming endpoint --- server/src/client_api.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 18e60133..40e8ebec 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -36,6 +36,18 @@ pub async fn get_features( ) -> EdgeJsonResult { resolve_features(edge_token, features_cache, token_cache, filter_query, req).await } + +#[get("/streaming")] +pub async fn stream_features( + edge_token: EdgeToken, + features_cache: Data>, + token_cache: Data>, + filter_query: Query, + req: HttpRequest, +) -> EdgeJsonResult { + resolve_features(edge_token, features_cache, token_cache, filter_query, req).await +} + #[utoipa::path( context_path = "/api/client", params(FeatureFilters), @@ -236,6 +248,7 @@ pub fn configure_client_api(cfg: &mut web::ServiceConfig) { crate::middleware::validate_token::validate_token, )) .service(get_features) + .service(stream_features) .service(get_feature) .service(register) .service(metrics) From 95ad05a1cb52d8898c8f8c5c1d6cdf2f7da174a3 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 4 Dec 2024 16:01:02 +0100 Subject: [PATCH 11/55] streaming-spike: impl streaming with actix web lab --- Cargo.lock | 129 +++++++++++++++++++++++++++++++++++++-- server/Cargo.toml | 1 + server/src/client_api.rs | 26 +++++++- 3 files changed, 149 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3915d7cd..57cacbcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" dependencies = [ "actix-utils", "actix-web", - "derive_more", + "derive_more 0.99.18", "futures-util", "log", "once_cell", @@ -51,7 +51,7 @@ dependencies = [ "brotli", "bytes", "bytestring", - "derive_more", + "derive_more 0.99.18", "encoding_rs", "flate2", "futures-core", @@ -229,7 +229,7 @@ dependencies = [ "bytestring", "cfg-if", "cookie", - "derive_more", + "derive_more 0.99.18", "encoding_rs", "futures-core", "futures-util", @@ -263,6 +263,55 @@ dependencies = [ "syn 2.0.82", ] +[[package]] +name = "actix-web-lab" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee75923689132fc5fb57ccc5bb98d25bb214796a29cd505844eb3b42daf11df0" +dependencies = [ + "actix-http", + "actix-router", + "actix-service", + "actix-utils", + "actix-web", + "actix-web-lab-derive", + "ahash", + "arc-swap", + "bytes", + "bytestring", + "csv", + "derive_more 1.0.0", + "form_urlencoded", + "futures-core", + "futures-util", + "http 0.2.12", + "impl-more", + "itertools 0.13.0", + "local-channel", + "mediatype", + "mime", + "pin-project-lite", + "regex", + "serde", + "serde_html_form", + "serde_json", + "serde_path_to_error", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "actix-web-lab-derive" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c221da13534b9352f3f79fcbbd6095f6d8aee63bdf1da8a73d36f9eeea17d5a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -444,7 +493,7 @@ dependencies = [ "base64 0.22.1", "bytes", "cfg-if", - "derive_more", + "derive_more 0.99.18", "futures-core", "futures-util", "h2 0.3.26", @@ -1281,6 +1330,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "darling" version = "0.20.10" @@ -1405,6 +1475,27 @@ dependencies = [ "syn 2.0.82", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", + "unicode-xid", +] + [[package]] name = "digest" version = "0.10.7" @@ -2378,6 +2469,12 @@ dependencies = [ "digest", ] +[[package]] +name = "mediatype" +version = "0.19.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8878cd8d1b3c8c8ae4b2ba0a36652b7cf192f618a599a7fbdfa25cffd4ea72dd" + [[package]] name = "memchr" version = "2.7.4" @@ -3441,6 +3538,19 @@ dependencies = [ "syn 2.0.82", ] +[[package]] +name = "serde_html_form" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de514ef58196f1fc96dcaef80fe6170a1ce6215df9687a93fe8300e773fefc5" +dependencies = [ + "form_urlencoded", + "indexmap 2.6.0", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_json" version = "1.0.132" @@ -3453,6 +3563,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_qs" version = "0.13.0" @@ -4225,6 +4345,7 @@ dependencies = [ "actix-middleware-etag", "actix-service", "actix-web", + "actix-web-lab", "ahash", "anyhow", "async-trait", diff --git a/server/Cargo.toml b/server/Cargo.toml index deb9c8b4..a8170fd8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -29,6 +29,7 @@ actix-http = "3.9.0" actix-middleware-etag = "0.4.2" actix-service = "2.0.2" actix-web = { version = "4.9.0", features = ["rustls-0_23", "compress-zstd"] } +actix-web-lab = "0.23.0" ahash = "0.8.11" anyhow = "1.0.91" async-trait = "0.1.83" diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 40e8ebec..42ab6cc4 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use crate::error::EdgeError; use crate::filters::{ filter_client_features, name_match_filter, name_prefix_filter, project_filter, FeatureFilterSet, @@ -9,8 +11,10 @@ use crate::types::{ self, BatchMetricsRequestBody, EdgeJsonResult, EdgeResult, EdgeToken, FeatureFilters, }; use actix_web::web::{self, Data, Json, Query}; -use actix_web::{get, post, HttpRequest, HttpResponse}; +use actix_web::{get, post, HttpRequest, HttpResponse, Responder}; +use actix_web_lab::sse; use dashmap::DashMap; +use tokio::time::sleep; use unleash_types::client_features::{ClientFeature, ClientFeatures}; use unleash_types::client_metrics::{ClientApplication, ClientMetrics, ConnectVia}; @@ -44,8 +48,24 @@ pub async fn stream_features( token_cache: Data>, filter_query: Query, req: HttpRequest, -) -> EdgeJsonResult { - resolve_features(edge_token, features_cache, token_cache, filter_query, req).await +) -> impl Responder { + let (sender, receiver) = tokio::sync::mpsc::channel(2); + + actix_web::rt::spawn(async move { + loop { + // let time = time::OffsetDateTime::now_utc(); + let msg = sse::Data::new("server event").event("timestamp"); + + if sender.send(msg.into()).await.is_err() { + tracing::warn!("client disconnected; could not send SSE message"); + break; + } + + sleep(Duration::from_secs(10)).await; + } + }); + + sse::Sse::from_infallible_receiver(receiver).with_keep_alive(Duration::from_secs(3)) } #[utoipa::path( From 232c978adf9d46a5c1c1678e43db8e0609d24f60 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 4 Dec 2024 16:03:06 +0100 Subject: [PATCH 12/55] streaming-spike: send whole feature set at interval --- server/src/client_api.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 42ab6cc4..b6ab6d56 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -53,11 +53,24 @@ pub async fn stream_features( actix_web::rt::spawn(async move { loop { - // let time = time::OffsetDateTime::now_utc(); - let msg = sse::Data::new("server event").event("timestamp"); + let data = resolve_features( + edge_token.clone(), + features_cache.clone(), + token_cache.clone(), + filter_query.clone(), + req.clone(), + ) + .await; + + if let Ok(data) = data { + let msg = sse::Data::new_json(data).unwrap().event("update"); - if sender.send(msg.into()).await.is_err() { - tracing::warn!("client disconnected; could not send SSE message"); + if sender.send(msg.into()).await.is_err() { + tracing::warn!("client disconnected; could not send SSE message"); + break; + } + } else { + tracing::warn!("whoops; data is err"); break; } From a20795718e5b7caa94a0d77ebb26ef8539513c41 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 4 Dec 2024 16:48:48 +0100 Subject: [PATCH 13/55] streaming-spike: improve handling of events --- server/src/http/feature_refresher.rs | 37 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 19a26825..20d6960e 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -334,25 +334,44 @@ impl FeatureRefresher { tokio::spawn(async move { let mut stream = es_client .stream() - .map_ok(move |event| { + .map_ok(move |sse| { let token = token.clone(); let refresher = refresher.clone(); async move { - match event { - eventsource_client::SSE::Event(_) => { - debug!("Got SSE event, refreshing features"); + match sse { + eventsource_client::SSE::Event(event) + if event.event_type == "unleash-connected" => + { + debug!( + "Connected to unleash! Here's the initial flag payload: {:#?}", + event.data + ); refresher .refresh_single(TokenRefresh::new(token, None)) .await; } + eventsource_client::SSE::Event(event) + if event.event_type == "unleash-updated" => + { + debug!( + "Got an unleash updated event. Here's the data: {:#?}", + event.data + ); + } + eventsource_client::SSE::Event(event) => { + debug!( + "Got SSE event that wasn't updated, ignoring. event: {:#?}", + event + ); + } eventsource_client::SSE::Connected(_) => { - debug!("SSE Connected, performing initial refresh"); - refresher - .refresh_single(TokenRefresh::new(token, None)) - .await; + debug!("SSE Connectection established"); } _ => { - debug!("Got a different SSE event {:#?}", event); + debug!( + "Got a different SSE event (probably a comment): {:#?}", + sse + ); } } } From 280c6a50a89550a47f543dd198e9af644d52760f Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 4 Dec 2024 19:04:30 +0100 Subject: [PATCH 14/55] streaming-spike: explicit comment handling --- server/src/http/feature_refresher.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 20d6960e..58d16531 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -343,8 +343,7 @@ impl FeatureRefresher { if event.event_type == "unleash-connected" => { debug!( - "Connected to unleash! Here's the initial flag payload: {:#?}", - event.data + "Connected to unleash! I should populate my flag cache now.", ); refresher .refresh_single(TokenRefresh::new(token, None)) @@ -354,25 +353,23 @@ impl FeatureRefresher { if event.event_type == "unleash-updated" => { debug!( - "Got an unleash updated event. Here's the data: {:#?}", - event.data + "Got an unleash updated event. I should update my cache and notify listeners.", ); + + } eventsource_client::SSE::Event(event) => { debug!( - "Got SSE event that wasn't updated, ignoring. event: {:#?}", + "Got an SSE event that I wasn't expecting: {:#?}", event ); } eventsource_client::SSE::Connected(_) => { debug!("SSE Connectection established"); } - _ => { - debug!( - "Got a different SSE event (probably a comment): {:#?}", - sse - ); - } + eventsource_client::SSE::Comment(_) => { + // purposefully left blank. + }, } } }) From 00c42e8b0b4affccc4bf9decc8eb8161809b654a Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 10:38:53 +0100 Subject: [PATCH 15/55] streaming-spike: dirty impl of broadcaster --- Cargo.lock | 3 + server/Cargo.toml | 3 + server/src/client_api.rs | 79 ++++++++++++-------- server/src/http/broadcaster.rs | 105 +++++++++++++++++++++++++++ server/src/http/feature_refresher.rs | 4 + server/src/http/mod.rs | 1 + 6 files changed, 166 insertions(+), 29 deletions(-) create mode 100644 server/src/http/broadcaster.rs diff --git a/Cargo.lock b/Cargo.lock index 57cacbcd..3c5b7f56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4362,6 +4362,7 @@ dependencies = [ "eventsource-client", "futures", "futures-core", + "futures-util", "iter_tools", "itertools 0.13.0", "lazy_static", @@ -4371,6 +4372,7 @@ dependencies = [ "opentelemetry-prometheus", "opentelemetry-semantic-conventions", "opentelemetry_sdk", + "parking_lot", "prometheus", "prometheus-reqwest-remote-write", "prometheus-static-metric", @@ -4389,6 +4391,7 @@ dependencies = [ "testcontainers", "testcontainers-modules", "tokio", + "tokio-stream", "tracing", "tracing-subscriber", "tracing-test", diff --git a/server/Cargo.toml b/server/Cargo.toml index a8170fd8..8ade1950 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -44,6 +44,7 @@ dashmap = "6.0.1" eventsource-client = "0.13.0" futures = "0.3.30" futures-core = "0.3.30" +futures-util = "0.3.31" iter_tools = "0.24.0" itertools = "0.13.0" lazy_static = "1.4.0" @@ -57,6 +58,7 @@ opentelemetry_sdk = { version = "0.24.0", features = [ "serde_json", "logs", ] } +parking_lot = "0.12.3" prometheus = { version = "0.13.4", features = ["process"] } prometheus-reqwest-remote-write = { version = "0.2.1" } prometheus-static-metric = "0.5.1" @@ -89,6 +91,7 @@ tokio = { version = "1.41.0", features = [ "tracing", "fs", ] } +tokio-stream = "0.1.16" tracing = { version = "0.1.40", features = ["log"] } tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] } ulid = "1.1.2" diff --git a/server/src/client_api.rs b/server/src/client_api.rs index b6ab6d56..d0963ed6 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -1,4 +1,9 @@ +use actix_web_lab::{ + sse::{self, Sse}, + util::InfallibleStream, +}; use std::time::Duration; +use tokio_stream::wrappers::ReceiverStream; use crate::error::EdgeError; use crate::filters::{ @@ -12,7 +17,6 @@ use crate::types::{ }; use actix_web::web::{self, Data, Json, Query}; use actix_web::{get, post, HttpRequest, HttpResponse, Responder}; -use actix_web_lab::sse; use dashmap::DashMap; use tokio::time::sleep; use unleash_types::client_features::{ClientFeature, ClientFeatures}; @@ -49,36 +53,53 @@ pub async fn stream_features( filter_query: Query, req: HttpRequest, ) -> impl Responder { - let (sender, receiver) = tokio::sync::mpsc::channel(2); - - actix_web::rt::spawn(async move { - loop { - let data = resolve_features( - edge_token.clone(), - features_cache.clone(), - token_cache.clone(), - filter_query.clone(), - req.clone(), - ) - .await; - - if let Ok(data) = data { - let msg = sse::Data::new_json(data).unwrap().event("update"); - - if sender.send(msg.into()).await.is_err() { - tracing::warn!("client disconnected; could not send SSE message"); - break; - } - } else { - tracing::warn!("whoops; data is err"); - break; - } + // .map(|refresher| refresher.broadcaster.new_client()) + match req.app_data::>() { + Some(refresher) => refresher.broadcaster.new_client().await, - sleep(Duration::from_secs(10)).await; - } - }); + None => todo!(), + } - sse::Sse::from_infallible_receiver(receiver).with_keep_alive(Duration::from_secs(3)) + // let broadcaster = match req.app_data::>() { + // Some(refresher) => { + // refresher.broadcaster.new_client().await + // } + // None => features_cache + // .get(&cache_key(&validated_token)) + // .map(|client_features| filter_client_features(&client_features, &filter_set)) + // .ok_or(EdgeError::ClientCacheError), + // }?; + + // let (sender, receiver) = tokio::sync::mpsc::channel(2); + + // actix_web::rt::spawn(async move { + // loop { + // let data = resolve_features( + // edge_token.clone(), + // features_cache.clone(), + // token_cache.clone(), + // filter_query.clone(), + // req.clone(), + // ) + // .await; + + // if let Ok(data) = data { + // let msg = sse::Data::new_json(data).unwrap().event("update"); + + // if sender.send(msg.into()).await.is_err() { + // tracing::warn!("client disconnected; could not send SSE message"); + // break; + // } + // } else { + // tracing::warn!("whoops; data is err"); + // break; + // } + + // sleep(Duration::from_secs(10)).await; + // } + // }); + + // sse::Sse::from_infallible_receiver(receiver).with_keep_alive(Duration::from_secs(3)) } #[utoipa::path( diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs new file mode 100644 index 00000000..2dfbd3b0 --- /dev/null +++ b/server/src/http/broadcaster.rs @@ -0,0 +1,105 @@ +use std::{sync::Arc, time::Duration}; + +use actix_web::rt::time::interval; +use actix_web::web::Json; +use actix_web_lab::{ + sse::{self, Sse}, + util::InfallibleStream, +}; +use futures_util::future; +use parking_lot::Mutex; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use unleash_types::client_features::ClientFeatures; + +pub struct Broadcaster { + inner: Mutex, +} + +#[derive(Debug, Clone, Default)] +struct BroadcasterInner { + clients: Vec>, +} + +impl Broadcaster { + /// Constructs new broadcaster and spawns ping loop. + pub fn create() -> Arc { + let this = Arc::new(Broadcaster { + inner: Mutex::new(BroadcasterInner::default()), + }); + + Broadcaster::spawn_ping(Arc::clone(&this)); + + this + } + + /// Pings clients every 30 seconds to see if they are alive and remove them from the broadcast + /// list if not. + fn spawn_ping(this: Arc) { + actix_web::rt::spawn(async move { + let mut interval = interval(Duration::from_secs(30)); + + loop { + interval.tick().await; + this.remove_stale_clients().await; + } + }); + } + + /// Removes all non-responsive clients from broadcast list. + async fn remove_stale_clients(&self) { + let clients = self.inner.lock().clients.clone(); + + let mut ok_clients = Vec::new(); + + for client in clients { + if client + .send(sse::Event::Comment("keep-alive".into())) + .await + .is_ok() + { + ok_clients.push(client.clone()); + } + } + + self.inner.lock().clients = ok_clients; + } + + /// Registers client with broadcaster, returning an SSE response body. + pub async fn new_client(&self) -> Sse>> { + let (tx, rx) = mpsc::channel(10); + + tx.send(sse::Data::new("").event("unleash-connected").into()) + .await + .unwrap(); + + self.inner.lock().clients.push(tx); + + Sse::from_infallible_receiver(rx).with_keep_alive(Duration::from_secs(30)) + } + + // /// Broadcasts `data` to all clients. + // pub async fn broadcast(&self, data: &Json) { + // let clients = self.inner.lock().clients.clone(); + + // let send_futures = clients + // .iter() + // .map(|client| client.send(sse::Data::new_json(data).unwrap().event("update"))); + + // // try to send to all clients, ignoring failures + // // disconnected clients will get swept up by `remove_stale_clients` + // let _ = future::join_all(send_futures).await; + // } + /// Broadcasts `msg` to all clients. + pub async fn broadcast(&self, msg: &str) { + let clients = self.inner.lock().clients.clone(); + + let send_futures = clients + .iter() + .map(|client| client.send(sse::Data::new(msg).into())); + + // try to send to all clients, ignoring failures + // disconnected clients will get swept up by `remove_stale_clients` + let _ = future::join_all(send_futures).await; + } +} diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 58d16531..715b1188 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -25,6 +25,7 @@ use crate::{ types::{ClientFeaturesRequest, ClientFeaturesResponse, EdgeToken, TokenRefresh}, }; +use super::broadcaster::Broadcaster; use super::unleash_client::UnleashClient; fn frontend_token_is_covered_by_tokens( @@ -104,6 +105,7 @@ pub struct FeatureRefresher { pub persistence: Option>, pub strict: bool, pub app_name: String, + pub broadcaster: Arc, } impl Default for FeatureRefresher { @@ -117,6 +119,7 @@ impl Default for FeatureRefresher { persistence: None, strict: true, app_name: "unleash_edge".into(), + broadcaster: Broadcaster::create(), } } } @@ -162,6 +165,7 @@ impl FeatureRefresher { persistence, strict, app_name: app_name.into(), + broadcaster: Broadcaster::create(), } } diff --git a/server/src/http/mod.rs b/server/src/http/mod.rs index a550af88..d8629d39 100644 --- a/server/src/http/mod.rs +++ b/server/src/http/mod.rs @@ -1,4 +1,5 @@ #[cfg(not(tarpaulin_include))] pub mod background_send_metrics; +pub mod broadcaster; pub mod feature_refresher; pub mod unleash_client; From c798ba1f6b04f229e0a536227aa283c936f409a2 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 10:59:20 +0100 Subject: [PATCH 16/55] streaming-spike: broadcast fewer errors --- server/src/client_api.rs | 16 ++++------------ server/src/http/broadcaster.rs | 23 ++++++++++------------- server/src/http/feature_refresher.rs | 6 ++++-- server/src/main.rs | 1 + 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index d0963ed6..9cd65ebd 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -1,10 +1,3 @@ -use actix_web_lab::{ - sse::{self, Sse}, - util::InfallibleStream, -}; -use std::time::Duration; -use tokio_stream::wrappers::ReceiverStream; - use crate::error::EdgeError; use crate::filters::{ filter_client_features, name_match_filter, name_prefix_filter, project_filter, FeatureFilterSet, @@ -18,7 +11,6 @@ use crate::types::{ use actix_web::web::{self, Data, Json, Query}; use actix_web::{get, post, HttpRequest, HttpResponse, Responder}; use dashmap::DashMap; -use tokio::time::sleep; use unleash_types::client_features::{ClientFeature, ClientFeatures}; use unleash_types::client_metrics::{ClientApplication, ClientMetrics, ConnectVia}; @@ -47,10 +39,10 @@ pub async fn get_features( #[get("/streaming")] pub async fn stream_features( - edge_token: EdgeToken, - features_cache: Data>, - token_cache: Data>, - filter_query: Query, + _edge_token: EdgeToken, + _features_cache: Data>, + _token_cache: Data>, + _filter_query: Query, req: HttpRequest, ) -> impl Responder { // .map(|refresher| refresher.broadcaster.new_client()) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 2dfbd3b0..5a5c1196 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -1,16 +1,14 @@ use std::{sync::Arc, time::Duration}; use actix_web::rt::time::interval; -use actix_web::web::Json; use actix_web_lab::{ - sse::{self, Sse}, + sse::{self, Event, Sse}, util::InfallibleStream, }; use futures_util::future; use parking_lot::Mutex; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; -use unleash_types::client_features::ClientFeatures; pub struct Broadcaster { inner: Mutex, @@ -66,6 +64,7 @@ impl Broadcaster { } /// Registers client with broadcaster, returning an SSE response body. + /// should take the current feature set as input and send it to the client. pub async fn new_client(&self) -> Sse>> { let (tx, rx) = mpsc::channel(10); @@ -78,18 +77,16 @@ impl Broadcaster { Sse::from_infallible_receiver(rx).with_keep_alive(Duration::from_secs(30)) } - // /// Broadcasts `data` to all clients. - // pub async fn broadcast(&self, data: &Json) { - // let clients = self.inner.lock().clients.clone(); + /// re-~roadcasts `data` to all clients. + pub async fn rebroadcast(&self, data: Event) { + let clients = self.inner.lock().clients.clone(); - // let send_futures = clients - // .iter() - // .map(|client| client.send(sse::Data::new_json(data).unwrap().event("update"))); + let send_futures = clients.iter().map(|client| client.send(data.clone())); - // // try to send to all clients, ignoring failures - // // disconnected clients will get swept up by `remove_stale_clients` - // let _ = future::join_all(send_futures).await; - // } + // try to send to all clients, ignoring failures + // disconnected clients will get swept up by `remove_stale_clients` + let _ = future::join_all(send_futures).await; + } /// Broadcasts `msg` to all clients. pub async fn broadcast(&self, msg: &str) { let clients = self.inner.lock().clients.clone(); diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 715b1188..d05b4ddd 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::{sync::Arc, time::Duration}; use actix_web::http::header::EntityTag; +use actix_web_lab::sse::Data; use chrono::Utc; use dashmap::DashMap; use eventsource_client::Client; @@ -359,8 +360,9 @@ impl FeatureRefresher { debug!( "Got an unleash updated event. I should update my cache and notify listeners.", ); - - + let data = Data::new_json(event.data).unwrap().event("unleash-updated"); + // self.broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; + // self.broadcaster.broadcast("got an update".clone).await; } eventsource_client::SSE::Event(event) => { debug!( diff --git a/server/src/main.rs b/server/src/main.rs index 3e16bc65..af488c29 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -77,6 +77,7 @@ async fn main() -> Result<(), anyhow::Error> { let openapi = openapi::ApiDoc::openapi(); let refresher_for_app_data = feature_refresher.clone(); let prom_registry_for_write = metrics_handler.registry.clone(); + let server = HttpServer::new(move || { let qs_config = serde_qs::actix::QsQueryConfig::default().qs_config(serde_qs::Config::new(5, false)); From e44e7be5910219e46a02b832aad4662226da73bf Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 11:07:42 +0100 Subject: [PATCH 17/55] streaming-spike: verrrry rough rebroadcast --- server/src/http/feature_refresher.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index d05b4ddd..3a05292f 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -336,12 +336,15 @@ impl FeatureRefresher { }; let refresher = self.clone(); + let broadcaster = self.broadcaster.clone(); + tokio::spawn(async move { let mut stream = es_client .stream() .map_ok(move |sse| { let token = token.clone(); let refresher = refresher.clone(); + let broadcaster = broadcaster.clone(); async move { match sse { eventsource_client::SSE::Event(event) @@ -361,7 +364,7 @@ impl FeatureRefresher { "Got an unleash updated event. I should update my cache and notify listeners.", ); let data = Data::new_json(event.data).unwrap().event("unleash-updated"); - // self.broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; + broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; // self.broadcaster.broadcast("got an update".clone).await; } eventsource_client::SSE::Event(event) => { From 9aeaa532b87818e85df9ba58813d13e29bf863fb Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 11:32:35 +0100 Subject: [PATCH 18/55] streaming-spike: rebroadcast streaming updates --- server/src/http/feature_refresher.rs | 48 ++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 3a05292f..503b6b9e 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -353,9 +353,10 @@ impl FeatureRefresher { debug!( "Connected to unleash! I should populate my flag cache now.", ); - refresher - .refresh_single(TokenRefresh::new(token, None)) - .await; + + let features: ClientFeatures = + serde_json::from_str(&event.data).unwrap(); + refresher.handle_client_features_updated(TokenRefresh::new(token, None), features); } eventsource_client::SSE::Event(event) if event.event_type == "unleash-updated" => @@ -363,6 +364,11 @@ impl FeatureRefresher { debug!( "Got an unleash updated event. I should update my cache and notify listeners.", ); + + let features: ClientFeatures = + serde_json::from_str(&event.data).unwrap(); + refresher.handle_client_features_updated(TokenRefresh::new(token, None), features); + let data = Data::new_json(event.data).unwrap().event("unleash-updated"); broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; // self.broadcaster.broadcast("got an update".clone).await; @@ -414,6 +420,42 @@ impl FeatureRefresher { } } + fn handle_client_features_updated(&self, refresh: TokenRefresh, features: ClientFeatures) { + debug!("Handling client features update."); + let key = cache_key(&refresh.token); + let etag = refresh.etag; + self.update_last_refresh(&refresh.token, etag, features.features.len()); + self.features_cache + .entry(key.clone()) + .and_modify(|existing_data| { + let updated_data = update_client_features(&refresh.token, existing_data, &features); + *existing_data = updated_data; + }) + .or_insert_with(|| features.clone()); + self.engine_cache + .entry(key.clone()) + .and_modify(|engine| { + if let Some(f) = self.features_cache.get(&key) { + let mut new_state = EngineState::default(); + let warnings = new_state.take_state(f.clone()); + if let Some(warnings) = warnings { + warn!("The following toggle failed to compile and will be defaulted to off: {warnings:?}"); + }; + *engine = new_state; + + } + }) + .or_insert_with(|| { + let mut new_state = EngineState::default(); + + let warnings = new_state.take_state(features); + if let Some(warnings) = warnings { + warn!("The following toggle failed to compile and will be defaulted to off: {warnings:?}"); + }; + new_state + }); + } + pub async fn refresh_single(&self, refresh: TokenRefresh) { let features_result = self .unleash_client From 41beb29c91568254f4c324f7cd251ff5b321b27d Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 11:32:50 +0100 Subject: [PATCH 19/55] streaming-spike: prepare to send message on connection A; --- server/src/client_api.rs | 66 ++++++++++------------------------------ 1 file changed, 16 insertions(+), 50 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 9cd65ebd..a0b43ba4 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -10,6 +10,7 @@ use crate::types::{ }; use actix_web::web::{self, Data, Json, Query}; use actix_web::{get, post, HttpRequest, HttpResponse, Responder}; +use aws_sdk_s3::config::endpoint::ResolveEndpoint; use dashmap::DashMap; use unleash_types::client_features::{ClientFeature, ClientFeatures}; use unleash_types::client_metrics::{ClientApplication, ClientMetrics, ConnectVia}; @@ -39,59 +40,24 @@ pub async fn get_features( #[get("/streaming")] pub async fn stream_features( - _edge_token: EdgeToken, - _features_cache: Data>, - _token_cache: Data>, - _filter_query: Query, + edge_token: EdgeToken, + features_cache: Data>, + token_cache: Data>, + filter_query: Query, req: HttpRequest, ) -> impl Responder { - // .map(|refresher| refresher.broadcaster.new_client()) - match req.app_data::>() { - Some(refresher) => refresher.broadcaster.new_client().await, - - None => todo!(), + let features = resolve_features( + edge_token, + features_cache, + token_cache, + filter_query, + req.clone(), + ) + .await; + match (req.app_data::>(), features) { + (Some(refresher), Ok(features)) => refresher.broadcaster.new_client().await, + _ => todo!(), } - - // let broadcaster = match req.app_data::>() { - // Some(refresher) => { - // refresher.broadcaster.new_client().await - // } - // None => features_cache - // .get(&cache_key(&validated_token)) - // .map(|client_features| filter_client_features(&client_features, &filter_set)) - // .ok_or(EdgeError::ClientCacheError), - // }?; - - // let (sender, receiver) = tokio::sync::mpsc::channel(2); - - // actix_web::rt::spawn(async move { - // loop { - // let data = resolve_features( - // edge_token.clone(), - // features_cache.clone(), - // token_cache.clone(), - // filter_query.clone(), - // req.clone(), - // ) - // .await; - - // if let Ok(data) = data { - // let msg = sse::Data::new_json(data).unwrap().event("update"); - - // if sender.send(msg.into()).await.is_err() { - // tracing::warn!("client disconnected; could not send SSE message"); - // break; - // } - // } else { - // tracing::warn!("whoops; data is err"); - // break; - // } - - // sleep(Duration::from_secs(10)).await; - // } - // }); - - // sse::Sse::from_infallible_receiver(receiver).with_keep_alive(Duration::from_secs(3)) } #[utoipa::path( From 0227a51feb765b6618d6d8cbf990f7e91eac0aca Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 11:41:03 +0100 Subject: [PATCH 20/55] streaming-spike: send data on connection --- server/src/client_api.rs | 2 +- server/src/http/broadcaster.rs | 19 ++++++++++++++----- server/src/http/feature_refresher.rs | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index a0b43ba4..7d0dc063 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -55,7 +55,7 @@ pub async fn stream_features( ) .await; match (req.app_data::>(), features) { - (Some(refresher), Ok(features)) => refresher.broadcaster.new_client().await, + (Some(refresher), Ok(features)) => refresher.broadcaster.new_client(features).await, _ => todo!(), } } diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 5a5c1196..12989d0a 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -1,6 +1,6 @@ use std::{sync::Arc, time::Duration}; -use actix_web::rt::time::interval; +use actix_web::{rt::time::interval, web::Json}; use actix_web_lab::{ sse::{self, Event, Sse}, util::InfallibleStream, @@ -9,6 +9,7 @@ use futures_util::future; use parking_lot::Mutex; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; +use unleash_types::client_features::ClientFeatures; pub struct Broadcaster { inner: Mutex, @@ -65,12 +66,20 @@ impl Broadcaster { /// Registers client with broadcaster, returning an SSE response body. /// should take the current feature set as input and send it to the client. - pub async fn new_client(&self) -> Sse>> { + pub async fn new_client( + &self, + features: Json, + ) -> Sse>> { let (tx, rx) = mpsc::channel(10); - tx.send(sse::Data::new("").event("unleash-connected").into()) - .await - .unwrap(); + tx.send( + sse::Data::new_json(features) + .unwrap() + .event("unleash-connected") + .into(), + ) + .await + .unwrap(); self.inner.lock().clients.push(tx); diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 503b6b9e..a31fe0df 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -369,7 +369,7 @@ impl FeatureRefresher { serde_json::from_str(&event.data).unwrap(); refresher.handle_client_features_updated(TokenRefresh::new(token, None), features); - let data = Data::new_json(event.data).unwrap().event("unleash-updated"); + let data = Data::new(event.data).event("unleash-updated"); broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; // self.broadcaster.broadcast("got an update".clone).await; } From 2abc29628937dc3fa6f918e9ef892205c3b6fa77 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 12:07:41 +0100 Subject: [PATCH 21/55] streaming-spike: remove redundant ping --- server/src/http/broadcaster.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 12989d0a..46d7efeb 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -83,7 +83,10 @@ impl Broadcaster { self.inner.lock().clients.push(tx); - Sse::from_infallible_receiver(rx).with_keep_alive(Duration::from_secs(30)) + Sse::from_infallible_receiver(rx) + // we're already using remove_stale_clients to clean up disconnected + // clients and send heartbeats. we probably don't need this. + // .with_keep_alive(Duration::from_secs(30)) } /// re-~roadcasts `data` to all clients. From f19be5a5517d9b94848732d7a6e0b8c19006e72b Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 13:11:51 +0100 Subject: [PATCH 22/55] broken: don't use this --- server/src/client_api.rs | 67 +++++++++++++++++++++++----- server/src/http/broadcaster.rs | 24 ++++++++-- server/src/http/feature_refresher.rs | 2 + 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 7d0dc063..1e66dbf0 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -12,6 +12,7 @@ use actix_web::web::{self, Data, Json, Query}; use actix_web::{get, post, HttpRequest, HttpResponse, Responder}; use aws_sdk_s3::config::endpoint::ResolveEndpoint; use dashmap::DashMap; +use tracing_subscriber::filter; use unleash_types::client_features::{ClientFeature, ClientFeatures}; use unleash_types::client_metrics::{ClientApplication, ClientMetrics, ConnectVia}; @@ -45,17 +46,19 @@ pub async fn stream_features( token_cache: Data>, filter_query: Query, req: HttpRequest, -) -> impl Responder { - let features = resolve_features( - edge_token, +) -> EdgeResult { + let (validated_token, filter_set, query) = + get_feature_filter(&edge_token, &token_cache, filter_query.clone())?; + let features = resolve_features_2( + query, + validated_token.clone(), + filter_set, features_cache, - token_cache, - filter_query, req.clone(), ) .await; match (req.app_data::>(), features) { - (Some(refresher), Ok(features)) => refresher.broadcaster.new_client(features).await, + (Some(refresher), Ok(features)) => Ok(refresher.broadcaster.new_client(features).await), _ => todo!(), } } @@ -83,13 +86,15 @@ pub async fn post_features( resolve_features(edge_token, features_cache, token_cache, filter_query, req).await } -async fn resolve_features( - edge_token: EdgeToken, - features_cache: Data>, - token_cache: Data>, +fn get_feature_filter( + edge_token: &EdgeToken, + token_cache: &Data>, filter_query: Query, - req: HttpRequest, -) -> EdgeJsonResult { +) -> EdgeResult<( + EdgeToken, + FeatureFilterSet, + unleash_types::client_features::Query, +)> { let validated_token = token_cache .get(&edge_token.token) .map(|e| e.value().clone()) @@ -111,6 +116,44 @@ async fn resolve_features( } .with_filter(project_filter(&validated_token)); + Ok((validated_token, filter_set, query)) +} + +async fn resolve_features_2( + query: unleash_types::client_features::Query, + validated_token: EdgeToken, + filter_set: FeatureFilterSet, + features_cache: Data>, + req: HttpRequest, +) -> EdgeJsonResult { + let client_features = match req.app_data::>() { + Some(refresher) => { + refresher + .features_for_filter(validated_token.clone(), &filter_set) + .await + } + None => features_cache + .get(&cache_key(&validated_token)) + .map(|client_features| filter_client_features(&client_features, &filter_set)) + .ok_or(EdgeError::ClientCacheError), + }?; + + Ok(Json(ClientFeatures { + query: Some(query), + ..client_features + })) +} + +async fn resolve_features( + edge_token: EdgeToken, + features_cache: Data>, + token_cache: Data>, + filter_query: Query, + req: HttpRequest, +) -> EdgeJsonResult { + let (validated_token, filter_set, query) = + get_feature_filter(&edge_token, &token_cache, filter_query.clone())?; + let client_features = match req.app_data::>() { Some(refresher) => { refresher diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 46d7efeb..50239408 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -9,13 +9,23 @@ use futures_util::future; use parking_lot::Mutex; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; -use unleash_types::client_features::ClientFeatures; +use unleash_types::client_features::{ClientFeatures, Query}; + +use crate::{filters::FeatureFilterSet, types::EdgeToken}; pub struct Broadcaster { inner: Mutex, } -#[derive(Debug, Clone, Default)] +// #[derive(Debug)] +struct StreamClient { + stream: mpsc::Sender, + token: EdgeToken, + filter_set: FeatureFilterSet, + query: Query, +} + +#[derive(Debug, Default)] struct BroadcasterInner { clients: Vec>, } @@ -68,6 +78,9 @@ impl Broadcaster { /// should take the current feature set as input and send it to the client. pub async fn new_client( &self, + // token: EdgeToken, + // filter_set: FeatureFilterSet, + // query: Query, features: Json, ) -> Sse>> { let (tx, rx) = mpsc::channel(10); @@ -81,7 +94,12 @@ impl Broadcaster { .await .unwrap(); - self.inner.lock().clients.push(tx); + self.inner.lock().clients.push(StreamClient { + stream: tx, + token, + filter_set, + query, + }); Sse::from_infallible_receiver(rx) // we're already using remove_stale_clients to clean up disconnected diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index a31fe0df..b15dbb85 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -369,6 +369,7 @@ impl FeatureRefresher { serde_json::from_str(&event.data).unwrap(); refresher.handle_client_features_updated(TokenRefresh::new(token, None), features); + let data = Data::new(event.data).event("unleash-updated"); broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; // self.broadcaster.broadcast("got an update".clone).await; @@ -420,6 +421,7 @@ impl FeatureRefresher { } } + // this is a copy of the handling in refresh_single. fn handle_client_features_updated(&self, refresh: TokenRefresh, features: ClientFeatures) { debug!("Handling client features update."); let key = cache_key(&refresh.token); From 323ff96595d7b8e20dcaf7a08a128b9edff70c8b Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 13:58:50 +0100 Subject: [PATCH 23/55] fix: unbreak edge; add comments --- server/src/client_api.rs | 12 +++++++-- server/src/http/broadcaster.rs | 40 +++++++++++++++++++++------- server/src/http/feature_refresher.rs | 13 ++++++--- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 1e66dbf0..58d134db 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -10,9 +10,7 @@ use crate::types::{ }; use actix_web::web::{self, Data, Json, Query}; use actix_web::{get, post, HttpRequest, HttpResponse, Responder}; -use aws_sdk_s3::config::endpoint::ResolveEndpoint; use dashmap::DashMap; -use tracing_subscriber::filter; use unleash_types::client_features::{ClientFeature, ClientFeatures}; use unleash_types::client_metrics::{ClientApplication, ClientMetrics, ConnectVia}; @@ -86,6 +84,9 @@ pub async fn post_features( resolve_features(edge_token, features_cache, token_cache, filter_query, req).await } +/// this was extracted from resolve_features because it gives us the necessary +/// values to filter flags and construct client feature responses later. It's a +/// standalone function and can be moved to a different file if necessary. fn get_feature_filter( edge_token: &EdgeToken, token_cache: &Data>, @@ -119,6 +120,10 @@ fn get_feature_filter( Ok((validated_token, filter_set, query)) } +/// This is the second half of resolve_features. The idea was that you don't +/// need to do the extraction work twice. The broadcaster shold be able to do +/// something like the Some arm of the match here, except we'll know that we +/// already have the refresher. async fn resolve_features_2( query: unleash_types::client_features::Query, validated_token: EdgeToken, @@ -144,6 +149,7 @@ async fn resolve_features_2( })) } +/// This is the same as it always was, except I extracted bits of it. async fn resolve_features( edge_token: EdgeToken, features_cache: Data>, @@ -323,6 +329,7 @@ pub fn configure_experimental_post_features( #[cfg(test)] mod tests { + use crate::http::broadcaster::Broadcaster; use crate::metrics::client_metrics::{ApplicationKey, MetricsBatch, MetricsKey}; use crate::types::{TokenType, TokenValidationStatus}; use std::collections::HashMap; @@ -1046,6 +1053,7 @@ mod tests { persistence: None, strict: false, app_name: "test-app".into(), + broadcaster: Broadcaster::create(), }); let token_validator = Arc::new(TokenValidator { unleash_client: unleash_client.clone(), diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 50239408..19b68d5b 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -1,3 +1,4 @@ +/// copied from https://github.com/actix/examples/blob/master/server-sent-events/src/broadcast.rs use std::{sync::Arc, time::Duration}; use actix_web::{rt::time::interval, web::Json}; @@ -17,7 +18,21 @@ pub struct Broadcaster { inner: Mutex, } -// #[derive(Debug)] +// this doesn't work because filter_set isn't clone. However, we can probably +// find a way around that. For instance, we can create a hash map / dash map of +// some client identifier to each filter set, so that we don't need to clone the +// filter set. + +// I'd thought at first that we could map the token to the filter set, but I +// think that might not be enough, as the filter set may also contain query +// param information, which can vary between uses of the same token. + +// It might be that the easiest way is to create an ID per client and use that. +// Then, when we drop clients, also drop their corresponding entries from the +// map. + +// #[derive(Debug, Clone)] + struct StreamClient { stream: mpsc::Sender, token: EdgeToken, @@ -75,7 +90,11 @@ impl Broadcaster { } /// Registers client with broadcaster, returning an SSE response body. - /// should take the current feature set as input and send it to the client. + /// The current impl takes the feature set as input and sends it to the client as a connected event. + /// + /// The commented-out arguments are what we'll need to store per client so + /// that we can properly filter / format the feature response when they get + /// updates later. pub async fn new_client( &self, // token: EdgeToken, @@ -94,12 +113,7 @@ impl Broadcaster { .await .unwrap(); - self.inner.lock().clients.push(StreamClient { - stream: tx, - token, - filter_set, - query, - }); + self.inner.lock().clients.push(tx); Sse::from_infallible_receiver(rx) // we're already using remove_stale_clients to clean up disconnected @@ -107,7 +121,12 @@ impl Broadcaster { // .with_keep_alive(Duration::from_secs(30)) } - /// re-~roadcasts `data` to all clients. + /// broadcasts a pre-formatted `data` event to all clients. + /// + /// The final implementation will probably not use this. Instead, it will + /// probably use each client's filters to determine the features to send. + /// We'll need to pass in either the full set of features or a way to filter + /// them. Both might work. pub async fn rebroadcast(&self, data: Event) { let clients = self.inner.lock().clients.clone(); @@ -117,7 +136,10 @@ impl Broadcaster { // disconnected clients will get swept up by `remove_stale_clients` let _ = future::join_all(send_futures).await; } + /// Broadcasts `msg` to all clients. + /// + /// This is the example implementation of the broadcast function. It's not used anywhere today. pub async fn broadcast(&self, msg: &str) { let clients = self.inner.lock().clients.clone(); diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index b15dbb85..a64712d8 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -306,6 +306,7 @@ impl FeatureRefresher { } } + /// This is where we set up a listener per token. pub async fn start_streaming_features_background_task(&self) { let refreshes = self.get_tokens_due_for_refresh(); for refresh in refreshes { @@ -347,6 +348,7 @@ impl FeatureRefresher { let broadcaster = broadcaster.clone(); async move { match sse { + // The first time we're connecting to Unleash. Just store the data. eventsource_client::SSE::Event(event) if event.event_type == "unleash-connected" => { @@ -354,10 +356,12 @@ impl FeatureRefresher { "Connected to unleash! I should populate my flag cache now.", ); + // very rough handling of client features. let features: ClientFeatures = serde_json::from_str(&event.data).unwrap(); refresher.handle_client_features_updated(TokenRefresh::new(token, None), features); } + // Unleash has updated. This is where we send data to listeners. eventsource_client::SSE::Event(event) if event.event_type == "unleash-updated" => { @@ -365,14 +369,17 @@ impl FeatureRefresher { "Got an unleash updated event. I should update my cache and notify listeners.", ); + // store the data locally let features: ClientFeatures = serde_json::from_str(&event.data).unwrap(); refresher.handle_client_features_updated(TokenRefresh::new(token, None), features); - + // send the data to the broadcaster. This should probably just send the new + // feature set OR even just a "filter flags" + // function. The broadcaster will take care + // of filtering the flags per listener. let data = Data::new(event.data).event("unleash-updated"); broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; - // self.broadcaster.broadcast("got an update".clone).await; } eventsource_client::SSE::Event(event) => { debug!( @@ -421,7 +428,7 @@ impl FeatureRefresher { } } - // this is a copy of the handling in refresh_single. + // this is a copy of the handling in refresh_single. Extracting just so we can handle the new flags in the same way without fetching them first. fn handle_client_features_updated(&self, refresh: TokenRefresh, features: ClientFeatures) { debug!("Handling client features update."); let key = cache_key(&refresh.token); From ae70a59dd456cc07ed41ca7105d619596e9c26ef Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 5 Dec 2024 16:47:29 +0100 Subject: [PATCH 24/55] extremely cursed immpl of working filters --- server/src/client_api.rs | 15 +++- server/src/http/broadcaster.rs | 102 ++++++++++++++++++++++----- server/src/http/feature_refresher.rs | 11 +-- 3 files changed, 102 insertions(+), 26 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 58d134db..24f96ed4 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -48,7 +48,7 @@ pub async fn stream_features( let (validated_token, filter_set, query) = get_feature_filter(&edge_token, &token_cache, filter_query.clone())?; let features = resolve_features_2( - query, + query.clone(), validated_token.clone(), filter_set, features_cache, @@ -56,7 +56,15 @@ pub async fn stream_features( ) .await; match (req.app_data::>(), features) { - (Some(refresher), Ok(features)) => Ok(refresher.broadcaster.new_client(features).await), + (Some(refresher), Ok(features)) => Ok(refresher + .broadcaster + .new_client( + validated_token, + filter_query.clone(), + query.clone(), + features, + ) + .await), _ => todo!(), } } @@ -330,6 +338,7 @@ pub fn configure_experimental_post_features( mod tests { use crate::http::broadcaster::Broadcaster; + use crate::internal_backstage::features; use crate::metrics::client_metrics::{ApplicationKey, MetricsBatch, MetricsKey}; use crate::types::{TokenType, TokenValidationStatus}; use std::collections::HashMap; @@ -1053,7 +1062,7 @@ mod tests { persistence: None, strict: false, app_name: "test-app".into(), - broadcaster: Broadcaster::create(), + broadcaster: Broadcaster::new(features_cache.clone()), }); let token_validator = Arc::new(TokenValidator { unleash_client: unleash_client.clone(), diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 19b68d5b..ae034728 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -1,21 +1,30 @@ /// copied from https://github.com/actix/examples/blob/master/server-sent-events/src/broadcast.rs -use std::{sync::Arc, time::Duration}; +use std::{collections::HashMap, sync::Arc, time::Duration}; -use actix_web::{rt::time::interval, web::Json}; +use actix_web::{ + rt::time::interval, + web::{Json, Query}, +}; use actix_web_lab::{ sse::{self, Event, Sse}, util::InfallibleStream, }; +use dashmap::DashMap; use futures_util::future; use parking_lot::Mutex; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; -use unleash_types::client_features::{ClientFeatures, Query}; +use unleash_types::client_features::ClientFeatures; -use crate::{filters::FeatureFilterSet, types::EdgeToken}; +use crate::{ + filters::{filter_client_features, name_prefix_filter, project_filter, FeatureFilterSet}, + tokens::cache_key, + types::{EdgeResult, EdgeToken, FeatureFilters}, +}; pub struct Broadcaster { inner: Mutex, + features_cache: Arc>, } // this doesn't work because filter_set isn't clone. However, we can probably @@ -31,25 +40,37 @@ pub struct Broadcaster { // Then, when we drop clients, also drop their corresponding entries from the // map. -// #[derive(Debug, Clone)] +#[derive(Debug, Clone)] struct StreamClient { stream: mpsc::Sender, + id: String, +} + +struct QueryStuff { token: EdgeToken, - filter_set: FeatureFilterSet, - query: Query, + filter_set: Query, + query: unleash_types::client_features::Query, +} + +impl std::fmt::Debug for QueryStuff { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "QueryStuff") + } } #[derive(Debug, Default)] struct BroadcasterInner { - clients: Vec>, + clients: Vec, + filters: HashMap, } impl Broadcaster { /// Constructs new broadcaster and spawns ping loop. - pub fn create() -> Arc { + pub fn new(features: Arc>) -> Arc { let this = Arc::new(Broadcaster { inner: Mutex::new(BroadcasterInner::default()), + features_cache: features, }); Broadcaster::spawn_ping(Arc::clone(&this)); @@ -78,6 +99,7 @@ impl Broadcaster { for client in clients { if client + .stream .send(sse::Event::Comment("keep-alive".into())) .await .is_ok() @@ -97,13 +119,25 @@ impl Broadcaster { /// updates later. pub async fn new_client( &self, - // token: EdgeToken, - // filter_set: FeatureFilterSet, - // query: Query, + token: EdgeToken, + filter_set: Query, + query: unleash_types::client_features::Query, features: Json, ) -> Sse>> { let (tx, rx) = mpsc::channel(10); + let token_string = token.token.clone(); + let query_stuff = QueryStuff { + token, + filter_set, + query, + }; + + self.inner + .lock() + .filters + .insert(token_string.clone(), query_stuff); + tx.send( sse::Data::new_json(features) .unwrap() @@ -113,7 +147,10 @@ impl Broadcaster { .await .unwrap(); - self.inner.lock().clients.push(tx); + self.inner.lock().clients.push(StreamClient { + stream: tx, + id: token_string, + }); Sse::from_infallible_receiver(rx) // we're already using remove_stale_clients to clean up disconnected @@ -130,22 +167,51 @@ impl Broadcaster { pub async fn rebroadcast(&self, data: Event) { let clients = self.inner.lock().clients.clone(); - let send_futures = clients.iter().map(|client| client.send(data.clone())); + let send_futures = clients + .iter() + .map(|client| client.stream.send(data.clone())); // try to send to all clients, ignoring failures // disconnected clients will get swept up by `remove_stale_clients` let _ = future::join_all(send_futures).await; } + fn get_query_filters( + filter_query: Query, + token: EdgeToken, + ) -> FeatureFilterSet { + let query_filters = filter_query.into_inner(); + + let filter_set = if let Some(name_prefix) = query_filters.name_prefix { + FeatureFilterSet::from(Box::new(name_prefix_filter(name_prefix))) + } else { + FeatureFilterSet::default() + } + .with_filter(project_filter(&token)); + filter_set + } + /// Broadcasts `msg` to all clients. /// /// This is the example implementation of the broadcast function. It's not used anywhere today. - pub async fn broadcast(&self, msg: &str) { + pub async fn broadcast(&self) { let clients = self.inner.lock().clients.clone(); - let send_futures = clients - .iter() - .map(|client| client.send(sse::Data::new(msg).into())); + let send_futures = clients.iter().map(|client| { + let binding = self.inner.lock(); + let query_stuff = binding.filters.get(&client.id).unwrap(); + let filter_set = Broadcaster::get_query_filters( + query_stuff.filter_set.clone(), + query_stuff.token.clone(), + ); + let features = self + .features_cache + .get(&cache_key(&query_stuff.token)) + .map(|client_features| filter_client_features(&client_features, &filter_set)); + // let features = get_features_for_filter(query_stuff.token.clone(), &filter_set).unwrap(); + let event = sse::Data::new_json(&features).unwrap().into(); + client.stream.send(event) + }); // try to send to all clients, ignoring failures // disconnected clients will get swept up by `remove_stale_clients` diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index a64712d8..0c4363e3 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -120,7 +120,7 @@ impl Default for FeatureRefresher { persistence: None, strict: true, app_name: "unleash_edge".into(), - broadcaster: Broadcaster::create(), + broadcaster: Broadcaster::new(Default::default()), } } } @@ -160,13 +160,13 @@ impl FeatureRefresher { FeatureRefresher { unleash_client, tokens_to_refresh: Arc::new(DashMap::default()), - features_cache: features, + features_cache: features.clone(), engine_cache: engines, refresh_interval: features_refresh_interval, persistence, strict, app_name: app_name.into(), - broadcaster: Broadcaster::create(), + broadcaster: Broadcaster::new(features.clone()), } } @@ -378,8 +378,9 @@ impl FeatureRefresher { // feature set OR even just a "filter flags" // function. The broadcaster will take care // of filtering the flags per listener. - let data = Data::new(event.data).event("unleash-updated"); - broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; + // let data = Data::new(event.data).event("unleash-updated"); + // broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; + broadcaster.broadcast().await; } eventsource_client::SSE::Event(event) => { debug!( From e097a900916e0876ff260d408a09cba47d5e2661 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 6 Dec 2024 09:26:52 +0100 Subject: [PATCH 25/55] simplify builder --- server/src/http/feature_refresher.rs | 34 ++++++++++------------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 0c4363e3..ae6b117f 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -307,34 +307,23 @@ impl FeatureRefresher { } /// This is where we set up a listener per token. - pub async fn start_streaming_features_background_task(&self) { + pub async fn start_streaming_features_background_task(&self) -> Result<(), anyhow::Error> { let refreshes = self.get_tokens_due_for_refresh(); for refresh in refreshes { let token = refresh.token.clone(); let streaming_url = format!("{}/client/streaming", self.unleash_client.urls.api_url); - let es_client = match eventsource_client::ClientBuilder::for_url(&streaming_url) { - Ok(builder) => match builder.header("Authorization", &token.token) { - Ok(builder) => builder - .reconnect( - eventsource_client::ReconnectOptions::reconnect(true) - .retry_initial(true) - .delay(Duration::from_secs(5)) - .delay_max(Duration::from_secs(30)) - .backoff_factor(2) - .build(), - ) + let es_client = eventsource_client::ClientBuilder::for_url(&streaming_url)? + .header("Authorization", &token.token)? + .reconnect( + eventsource_client::ReconnectOptions::reconnect(true) + .retry_initial(true) + .delay(Duration::from_secs(5)) + .delay_max(Duration::from_secs(30)) + .backoff_factor(2) .build(), - Err(e) => { - warn!("Failed to add authorization header for streaming: {}", e); - continue; - } - }, - Err(e) => { - warn!("Failed to create streaming client: {}", e); - continue; - } - }; + ) + .build(); let refresher = self.clone(); let broadcaster = self.broadcaster.clone(); @@ -404,6 +393,7 @@ impl FeatureRefresher { } }); } + Ok(()) } pub async fn start_refresh_features_background_task(&self) { From 0d657618c25b8d69d7c23d46fac098f8c7ae2a73 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 6 Dec 2024 11:24:21 +0100 Subject: [PATCH 26/55] wip: (broken) use active_connections hash map --- server/src/client_api.rs | 2 +- server/src/http/broadcaster.rs | 138 +++++++++++++++++++-------- server/src/http/feature_refresher.rs | 10 +- server/src/main.rs | 2 +- 4 files changed, 107 insertions(+), 45 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 24f96ed4..c646d92e 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -58,7 +58,7 @@ pub async fn stream_features( match (req.app_data::>(), features) { (Some(refresher), Ok(features)) => Ok(refresher .broadcaster - .new_client( + .connect( validated_token, filter_query.clone(), query.clone(), diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index ae034728..a956cec1 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -1,5 +1,10 @@ /// copied from https://github.com/actix/examples/blob/master/server-sent-events/src/broadcast.rs -use std::{collections::HashMap, sync::Arc, time::Duration}; +use std::{ + collections::HashMap, + hash::{Hash, Hasher}, + sync::Arc, + time::Duration, +}; use actix_web::{ rt::time::interval, @@ -12,21 +17,18 @@ use actix_web_lab::{ use dashmap::DashMap; use futures_util::future; use parking_lot::Mutex; -use tokio::sync::mpsc; +use serde::Serialize; +use tokio::{net::unix::pipe::Sender, sync::mpsc}; use tokio_stream::wrappers::ReceiverStream; -use unleash_types::client_features::ClientFeatures; +use unleash_types::client_features::{ClientFeatures, Query as FlagQuery}; use crate::{ + cli, filters::{filter_client_features, name_prefix_filter, project_filter, FeatureFilterSet}, tokens::cache_key, types::{EdgeResult, EdgeToken, FeatureFilters}, }; -pub struct Broadcaster { - inner: Mutex, - features_cache: Arc>, -} - // this doesn't work because filter_set isn't clone. However, we can probably // find a way around that. For instance, we can create a hash map / dash map of // some client identifier to each filter set, so that we don't need to clone the @@ -50,7 +52,7 @@ struct StreamClient { struct QueryStuff { token: EdgeToken, filter_set: Query, - query: unleash_types::client_features::Query, + query: FlagQuery, } impl std::fmt::Debug for QueryStuff { @@ -59,12 +61,36 @@ impl std::fmt::Debug for QueryStuff { } } -#[derive(Debug, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +struct QueryWrapper { + query: FlagQuery, +} + +impl Hash for QueryWrapper { + fn hash(&self, state: &mut H) { + serde_json::to_string(&self.query).unwrap().hash(state); + } +} + +#[derive(Clone, Debug)] +struct ClientGroup { + clients: Vec>, + filter_set: Query, + token: EdgeToken, +} + +#[derive(Default)] struct BroadcasterInner { + active_connections: HashMap, clients: Vec, filters: HashMap, } +pub struct Broadcaster { + inner: Mutex, + features_cache: Arc>, +} + impl Broadcaster { /// Constructs new broadcaster and spawns ping loop. pub fn new(features: Arc>) -> Arc { @@ -117,7 +143,7 @@ impl Broadcaster { /// The commented-out arguments are what we'll need to store per client so /// that we can properly filter / format the feature response when they get /// updates later. - pub async fn new_client( + pub async fn connect( &self, token: EdgeToken, filter_set: Query, @@ -126,17 +152,32 @@ impl Broadcaster { ) -> Sse>> { let (tx, rx) = mpsc::channel(10); - let token_string = token.token.clone(); - let query_stuff = QueryStuff { - token, - filter_set, - query, - }; + // let token_string = token.token.clone(); + // let query_stuff = QueryStuff { + // token: token.clone(), + // filter_set: filter_set.clone(), + // query: query.clone(), + // }; self.inner .lock() - .filters - .insert(token_string.clone(), query_stuff); + .active_connections + .entry(QueryWrapper { + query: query.clone(), + }) + .and_modify(|group| { + group.clients.push(tx.clone()); + }) + .or_insert(ClientGroup { + clients: vec![tx.clone()], + filter_set, + token, + }); + + // self.inner + // .lock() + // .filters + // .insert(token_string.clone(), query_stuff); tx.send( sse::Data::new_json(features) @@ -147,10 +188,10 @@ impl Broadcaster { .await .unwrap(); - self.inner.lock().clients.push(StreamClient { - stream: tx, - id: token_string, - }); + // self.inner.lock().clients.push(StreamClient { + // stream: tx, + // id: token_string, + // }); Sse::from_infallible_receiver(rx) // we're already using remove_stale_clients to clean up disconnected @@ -191,30 +232,51 @@ impl Broadcaster { filter_set } - /// Broadcasts `msg` to all clients. - /// - /// This is the example implementation of the broadcast function. It's not used anywhere today. + /// Broadcast new features to all clients. pub async fn broadcast(&self) { let clients = self.inner.lock().clients.clone(); + let active_connections = self.inner.lock().active_connections.clone(); - let send_futures = clients.iter().map(|client| { - let binding = self.inner.lock(); - let query_stuff = binding.filters.get(&client.id).unwrap(); - let filter_set = Broadcaster::get_query_filters( - query_stuff.filter_set.clone(), - query_stuff.token.clone(), - ); + let send_futures = Vec::new(); + for (query, group) in active_connections { + let filter_set = + Broadcaster::get_query_filters(group.filter_set.clone(), group.token.clone()); let features = self .features_cache - .get(&cache_key(&query_stuff.token)) + .get(&cache_key(&group.token)) .map(|client_features| filter_client_features(&client_features, &filter_set)); - // let features = get_features_for_filter(query_stuff.token.clone(), &filter_set).unwrap(); - let event = sse::Data::new_json(&features).unwrap().into(); - client.stream.send(event) - }); + let event: Event = sse::Data::new_json(&features).unwrap().into(); + for client in group.clients { + send_futures.push(client.stream.send(event.clone())); + } + // let send_futures = group + // .clients + // .iter() + // .map(|client| client.send(event.clone())); + } // try to send to all clients, ignoring failures // disconnected clients will get swept up by `remove_stale_clients` let _ = future::join_all(send_futures).await; + + // let send_futures = clients.iter().map(|client| { + // let binding = self.inner.lock(); + // let query_stuff = binding.filters.get(&client.id).unwrap(); + // let filter_set = Broadcaster::get_query_filters( + // query_stuff.filter_set.clone(), + // query_stuff.token.clone(), + // ); + // let features = self + // .features_cache + // .get(&cache_key(&query_stuff.token)) + // .map(|client_features| filter_client_features(&client_features, &filter_set)); + // // let features = get_features_for_filter(query_stuff.token.clone(), &filter_set).unwrap(); + // let event = sse::Data::new_json(&features).unwrap().into(); + // client.stream.send(event) + // }); + + // // try to send to all clients, ignoring failures + // // disconnected clients will get swept up by `remove_stale_clients` + // let _ = future::join_all(send_futures).await; } } diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index ae6b117f..516ab36a 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -307,7 +307,7 @@ impl FeatureRefresher { } /// This is where we set up a listener per token. - pub async fn start_streaming_features_background_task(&self) -> Result<(), anyhow::Error> { + pub async fn start_streaming_features_background_task(&self) -> anyhow::Result<()> { let refreshes = self.get_tokens_due_for_refresh(); for refresh in refreshes { let token = refresh.token.clone(); @@ -326,7 +326,7 @@ impl FeatureRefresher { .build(); let refresher = self.clone(); - let broadcaster = self.broadcaster.clone(); + // let broadcaster = self.broadcaster.clone(); tokio::spawn(async move { let mut stream = es_client @@ -334,7 +334,7 @@ impl FeatureRefresher { .map_ok(move |sse| { let token = token.clone(); let refresher = refresher.clone(); - let broadcaster = broadcaster.clone(); + // let broadcaster = broadcaster.clone(); async move { match sse { // The first time we're connecting to Unleash. Just store the data. @@ -369,7 +369,7 @@ impl FeatureRefresher { // of filtering the flags per listener. // let data = Data::new(event.data).event("unleash-updated"); // broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; - broadcaster.broadcast().await; + refresher.broadcaster.broadcast().await; } eventsource_client::SSE::Event(event) => { debug!( @@ -378,7 +378,7 @@ impl FeatureRefresher { ); } eventsource_client::SSE::Connected(_) => { - debug!("SSE Connectection established"); + debug!("SSE Connection established"); } eventsource_client::SSE::Comment(_) => { // purposefully left blank. diff --git a/server/src/main.rs b/server/src/main.rs index af488c29..b10732d9 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -153,7 +153,7 @@ async fn main() -> Result<(), anyhow::Error> { let refresher_for_background = feature_refresher.clone().unwrap(); tokio::spawn(async move { - refresher_for_background + let _ = refresher_for_background .start_streaming_features_background_task() .await; }); From 74fab321323f53d1d7c7df4962bce92aa70e5eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Fri, 6 Dec 2024 10:50:02 +0000 Subject: [PATCH 27/55] fix: make edge compile again --- server/src/http/broadcaster.rs | 52 ++++------------------------------ 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index a956cec1..7acc7176 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -152,13 +152,6 @@ impl Broadcaster { ) -> Sse>> { let (tx, rx) = mpsc::channel(10); - // let token_string = token.token.clone(); - // let query_stuff = QueryStuff { - // token: token.clone(), - // filter_set: filter_set.clone(), - // query: query.clone(), - // }; - self.inner .lock() .active_connections @@ -174,11 +167,6 @@ impl Broadcaster { token, }); - // self.inner - // .lock() - // .filters - // .insert(token_string.clone(), query_stuff); - tx.send( sse::Data::new_json(features) .unwrap() @@ -188,11 +176,6 @@ impl Broadcaster { .await .unwrap(); - // self.inner.lock().clients.push(StreamClient { - // stream: tx, - // id: token_string, - // }); - Sse::from_infallible_receiver(rx) // we're already using remove_stale_clients to clean up disconnected // clients and send heartbeats. we probably don't need this. @@ -234,49 +217,26 @@ impl Broadcaster { /// Broadcast new features to all clients. pub async fn broadcast(&self) { - let clients = self.inner.lock().clients.clone(); let active_connections = self.inner.lock().active_connections.clone(); - let send_futures = Vec::new(); - for (query, group) in active_connections { + let mut client_events = Vec::new(); + for (_query, group) in active_connections { let filter_set = Broadcaster::get_query_filters(group.filter_set.clone(), group.token.clone()); let features = self .features_cache .get(&cache_key(&group.token)) .map(|client_features| filter_client_features(&client_features, &filter_set)); - let event: Event = sse::Data::new_json(&features).unwrap().into(); + let event: Event = sse::Data::new_json(&features).unwrap().event("unleash-updated").into(); for client in group.clients { - send_futures.push(client.stream.send(event.clone())); + client_events.push((client, event.clone())); } - // let send_futures = group - // .clients - // .iter() - // .map(|client| client.send(event.clone())); } // try to send to all clients, ignoring failures // disconnected clients will get swept up by `remove_stale_clients` - let _ = future::join_all(send_futures).await; + let send_events = client_events.iter().map(|(client, event)| client.send(event.clone())); - // let send_futures = clients.iter().map(|client| { - // let binding = self.inner.lock(); - // let query_stuff = binding.filters.get(&client.id).unwrap(); - // let filter_set = Broadcaster::get_query_filters( - // query_stuff.filter_set.clone(), - // query_stuff.token.clone(), - // ); - // let features = self - // .features_cache - // .get(&cache_key(&query_stuff.token)) - // .map(|client_features| filter_client_features(&client_features, &filter_set)); - // // let features = get_features_for_filter(query_stuff.token.clone(), &filter_set).unwrap(); - // let event = sse::Data::new_json(&features).unwrap().into(); - // client.stream.send(event) - // }); - - // // try to send to all clients, ignoring failures - // // disconnected clients will get swept up by `remove_stale_clients` - // let _ = future::join_all(send_futures).await; + let _ = future::join_all(send_events).await; } } From 11423b9f8d258e7fca8d914ec112bbe449d70bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Fri, 6 Dec 2024 10:58:00 +0000 Subject: [PATCH 28/55] refactor: remove unused code --- server/src/http/broadcaster.rs | 89 ++++++++-------------------------- 1 file changed, 19 insertions(+), 70 deletions(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 7acc7176..16c1d52c 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -18,49 +18,16 @@ use dashmap::DashMap; use futures_util::future; use parking_lot::Mutex; use serde::Serialize; -use tokio::{net::unix::pipe::Sender, sync::mpsc}; +use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; use unleash_types::client_features::{ClientFeatures, Query as FlagQuery}; use crate::{ - cli, filters::{filter_client_features, name_prefix_filter, project_filter, FeatureFilterSet}, tokens::cache_key, - types::{EdgeResult, EdgeToken, FeatureFilters}, + types::{EdgeToken, FeatureFilters}, }; -// this doesn't work because filter_set isn't clone. However, we can probably -// find a way around that. For instance, we can create a hash map / dash map of -// some client identifier to each filter set, so that we don't need to clone the -// filter set. - -// I'd thought at first that we could map the token to the filter set, but I -// think that might not be enough, as the filter set may also contain query -// param information, which can vary between uses of the same token. - -// It might be that the easiest way is to create an ID per client and use that. -// Then, when we drop clients, also drop their corresponding entries from the -// map. - -#[derive(Debug, Clone)] - -struct StreamClient { - stream: mpsc::Sender, - id: String, -} - -struct QueryStuff { - token: EdgeToken, - filter_set: Query, - query: FlagQuery, -} - -impl std::fmt::Debug for QueryStuff { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "QueryStuff") - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize)] struct QueryWrapper { query: FlagQuery, @@ -82,8 +49,6 @@ struct ClientGroup { #[derive(Default)] struct BroadcasterInner { active_connections: HashMap, - clients: Vec, - filters: HashMap, } pub struct Broadcaster { @@ -119,22 +84,24 @@ impl Broadcaster { /// Removes all non-responsive clients from broadcast list. async fn remove_stale_clients(&self) { - let clients = self.inner.lock().clients.clone(); - - let mut ok_clients = Vec::new(); - - for client in clients { - if client - .stream - .send(sse::Event::Comment("keep-alive".into())) - .await - .is_ok() - { - ok_clients.push(client.clone()); - } - } + //let clients = self.inner.lock().clients.clone(); + let active_connections = self.inner.lock().active_connections.clone(); + + for (_query, mut group) in active_connections { + let mut ok_clients = Vec::new(); - self.inner.lock().clients = ok_clients; + for client in group.clients { + if client + .send(sse::Event::Comment("keep-alive".into())) + .await + .is_ok() + { + ok_clients.push(client.clone()); + } + } + + group.clients = ok_clients; + } } /// Registers client with broadcaster, returning an SSE response body. @@ -182,24 +149,6 @@ impl Broadcaster { // .with_keep_alive(Duration::from_secs(30)) } - /// broadcasts a pre-formatted `data` event to all clients. - /// - /// The final implementation will probably not use this. Instead, it will - /// probably use each client's filters to determine the features to send. - /// We'll need to pass in either the full set of features or a way to filter - /// them. Both might work. - pub async fn rebroadcast(&self, data: Event) { - let clients = self.inner.lock().clients.clone(); - - let send_futures = clients - .iter() - .map(|client| client.stream.send(data.clone())); - - // try to send to all clients, ignoring failures - // disconnected clients will get swept up by `remove_stale_clients` - let _ = future::join_all(send_futures).await; - } - fn get_query_filters( filter_query: Query, token: EdgeToken, From 30722179708ddc2a53f433f07ecbb00ad5c6046a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Fri, 6 Dec 2024 11:21:00 +0000 Subject: [PATCH 29/55] refactor: remove unused, fix client_api tests --- server/src/client_api.rs | 1 - server/src/http/broadcaster.rs | 1 - server/src/http/feature_refresher.rs | 1 - server/src/http/unleash_client.rs | 1 - 4 files changed, 4 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index c646d92e..348e7bea 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -338,7 +338,6 @@ pub fn configure_experimental_post_features( mod tests { use crate::http::broadcaster::Broadcaster; - use crate::internal_backstage::features; use crate::metrics::client_metrics::{ApplicationKey, MetricsBatch, MetricsKey}; use crate::types::{TokenType, TokenValidationStatus}; use std::collections::HashMap; diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 16c1d52c..c09cd817 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -84,7 +84,6 @@ impl Broadcaster { /// Removes all non-responsive clients from broadcast list. async fn remove_stale_clients(&self) { - //let clients = self.inner.lock().clients.clone(); let active_connections = self.inner.lock().active_connections.clone(); for (_query, mut group) in active_connections { diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 516ab36a..3af77a18 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; use std::{sync::Arc, time::Duration}; use actix_web::http::header::EntityTag; -use actix_web_lab::sse::Data; use chrono::Utc; use dashmap::DashMap; use eventsource_client::Client; diff --git a/server/src/http/unleash_client.rs b/server/src/http/unleash_client.rs index 631fdbbe..6397e842 100644 --- a/server/src/http/unleash_client.rs +++ b/server/src/http/unleash_client.rs @@ -500,7 +500,6 @@ mod tests { http::header::EntityTag, web, App, HttpResponse, }; - use capture_logger::{begin_capture, pop_captured}; use chrono::Duration; use unleash_types::client_features::{ClientFeature, ClientFeatures}; From ba0f10bd395f9341d0c65b43af71b6c07738c8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Fri, 6 Dec 2024 12:26:30 +0000 Subject: [PATCH 30/55] fix: use tokio::spawn instead of actix_web::rt::spawn in spawn_ping --- server/src/http/broadcaster.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index c09cd817..8cf4b1ea 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -72,7 +72,7 @@ impl Broadcaster { /// Pings clients every 30 seconds to see if they are alive and remove them from the broadcast /// list if not. fn spawn_ping(this: Arc) { - actix_web::rt::spawn(async move { + tokio::spawn(async move { let mut interval = interval(Duration::from_secs(30)); loop { From 9c86270dc7f26f71d0bf6b321034f009bf5393ad Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 6 Dec 2024 13:59:21 +0100 Subject: [PATCH 31/55] only compile streaming conditionally --- server/Cargo.toml | 3 +++ server/src/client_api.rs | 32 +++++++++++++++++----------- server/src/http/feature_refresher.rs | 8 +++++++ server/src/http/mod.rs | 1 + server/src/main.rs | 23 +++++++++++--------- 5 files changed, 44 insertions(+), 23 deletions(-) diff --git a/server/Cargo.toml b/server/Cargo.toml index 8ade1950..041c619b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -14,6 +14,9 @@ name = "unleash-edge" repository = "https://github.com/Unleash/unleash-edge" version = "19.6.2" +[features] +streaming = [] + [package.metadata.wix] upgrade-guid = "11E5D83A-3034-48BB-9A84-9F589EBD648C" path-guid = "6F606A3B-C7E9-43EC-8B6E-91D7B74F80FC" diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 348e7bea..758c8dd2 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -9,7 +9,9 @@ use crate::types::{ self, BatchMetricsRequestBody, EdgeJsonResult, EdgeResult, EdgeToken, FeatureFilters, }; use actix_web::web::{self, Data, Json, Query}; -use actix_web::{get, post, HttpRequest, HttpResponse, Responder}; +#[cfg(feature = "streaming")] +use actix_web::Responder; +use actix_web::{get, post, HttpRequest, HttpResponse}; use dashmap::DashMap; use unleash_types::client_features::{ClientFeature, ClientFeatures}; use unleash_types::client_metrics::{ClientApplication, ClientMetrics, ConnectVia}; @@ -37,6 +39,7 @@ pub async fn get_features( resolve_features(edge_token, features_cache, token_cache, filter_query, req).await } +#[cfg(feature = "streaming")] #[get("/streaming")] pub async fn stream_features( edge_token: EdgeToken, @@ -132,6 +135,7 @@ fn get_feature_filter( /// need to do the extraction work twice. The broadcaster shold be able to do /// something like the Some arm of the match here, except we'll know that we /// already have the refresher. +#[cfg(feature = "streaming")] async fn resolve_features_2( query: unleash_types::client_features::Query, validated_token: EdgeToken, @@ -311,18 +315,20 @@ pub async fn post_bulk_metrics( Ok(HttpResponse::Accepted().finish()) } pub fn configure_client_api(cfg: &mut web::ServiceConfig) { - cfg.service( - web::scope("/client") - .wrap(crate::middleware::as_async_middleware::as_async_middleware( - crate::middleware::validate_token::validate_token, - )) - .service(get_features) - .service(stream_features) - .service(get_feature) - .service(register) - .service(metrics) - .service(post_bulk_metrics), - ); + let client_scope = web::scope("/client") + .wrap(crate::middleware::as_async_middleware::as_async_middleware( + crate::middleware::validate_token::validate_token, + )) + .service(get_features) + .service(get_feature) + .service(register) + .service(metrics) + .service(post_bulk_metrics); + + #[cfg(feature = "streaming")] + let client_scope = client_scope.service(stream_features); + + cfg.service(client_scope); } pub fn configure_experimental_post_features( diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 3af77a18..c9ba0717 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -4,7 +4,9 @@ use std::{sync::Arc, time::Duration}; use actix_web::http::header::EntityTag; use chrono::Utc; use dashmap::DashMap; +#[cfg(feature = "streaming")] use eventsource_client::Client; +#[cfg(feature = "streaming")] use futures::TryStreamExt; use reqwest::StatusCode; use tracing::{debug, info, warn}; @@ -25,6 +27,7 @@ use crate::{ types::{ClientFeaturesRequest, ClientFeaturesResponse, EdgeToken, TokenRefresh}, }; +#[cfg(feature = "streaming")] use super::broadcaster::Broadcaster; use super::unleash_client::UnleashClient; @@ -105,6 +108,7 @@ pub struct FeatureRefresher { pub persistence: Option>, pub strict: bool, pub app_name: String, + #[cfg(feature = "streaming")] pub broadcaster: Arc, } @@ -119,6 +123,7 @@ impl Default for FeatureRefresher { persistence: None, strict: true, app_name: "unleash_edge".into(), + #[cfg(feature = "streaming")] broadcaster: Broadcaster::new(Default::default()), } } @@ -165,6 +170,7 @@ impl FeatureRefresher { persistence, strict, app_name: app_name.into(), + #[cfg(feature = "streaming")] broadcaster: Broadcaster::new(features.clone()), } } @@ -306,6 +312,7 @@ impl FeatureRefresher { } /// This is where we set up a listener per token. + #[cfg(feature = "streaming")] pub async fn start_streaming_features_background_task(&self) -> anyhow::Result<()> { let refreshes = self.get_tokens_due_for_refresh(); for refresh in refreshes { @@ -419,6 +426,7 @@ impl FeatureRefresher { } // this is a copy of the handling in refresh_single. Extracting just so we can handle the new flags in the same way without fetching them first. + #[cfg(feature = "streaming")] fn handle_client_features_updated(&self, refresh: TokenRefresh, features: ClientFeatures) { debug!("Handling client features update."); let key = cache_key(&refresh.token); diff --git a/server/src/http/mod.rs b/server/src/http/mod.rs index d8629d39..a7849f6a 100644 --- a/server/src/http/mod.rs +++ b/server/src/http/mod.rs @@ -1,5 +1,6 @@ #[cfg(not(tarpaulin_include))] pub mod background_send_metrics; +#[cfg(feature = "streaming")] pub mod broadcaster; pub mod feature_refresher; pub mod unleash_client; diff --git a/server/src/main.rs b/server/src/main.rs index b10732d9..1a14d0f6 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -150,13 +150,15 @@ async fn main() -> Result<(), anyhow::Error> { match schedule_args.mode { cli::EdgeMode::Edge(edge) => { - let refresher_for_background = feature_refresher.clone().unwrap(); - - tokio::spawn(async move { - let _ = refresher_for_background - .start_streaming_features_background_task() - .await; - }); + #[cfg(feature = "streaming")] + { + let refresher_for_background = feature_refresher.clone().unwrap(); + tokio::spawn(async move { + let _ = refresher_for_background + .start_streaming_features_background_task() + .await; + }); + } let refresher = feature_refresher.clone().unwrap(); let validator = token_validator_schedule.clone().unwrap(); @@ -166,9 +168,10 @@ async fn main() -> Result<(), anyhow::Error> { clean_shutdown(persistence.clone(), lazy_feature_cache.clone(), lazy_token_cache.clone(), metrics_cache_clone.clone(), feature_refresher.clone()).await; tracing::info!("Actix was shutdown properly"); }, - // _ = refresher.start_refresh_features_background_task() => { - // tracing::info!("Feature refresher unexpectedly shut down"); - // } + // #[cfg(not(feature = "streaming"))] // fix this somehow + _ = refresher.start_refresh_features_background_task() => { + tracing::info!("Feature refresher unexpectedly shut down"); + } _ = unleash_edge::http::background_send_metrics::send_metrics_task(metrics_cache_clone.clone(), refresher.clone(), edge.metrics_interval_seconds.try_into().unwrap()) => { tracing::info!("Metrics poster unexpectedly shut down"); } From 1d5d1386695e9b325cad4792444782031aeb851e Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 6 Dec 2024 14:10:08 +0100 Subject: [PATCH 32/55] also conditionally compile tests --- server/src/client_api.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 758c8dd2..5d107d41 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -343,6 +343,7 @@ pub fn configure_experimental_post_features( #[cfg(test)] mod tests { + #[cfg(feature = "streaming")] use crate::http::broadcaster::Broadcaster; use crate::metrics::client_metrics::{ApplicationKey, MetricsBatch, MetricsKey}; use crate::types::{TokenType, TokenValidationStatus}; @@ -1067,6 +1068,7 @@ mod tests { persistence: None, strict: false, app_name: "test-app".into(), + #[cfg(feature = "streaming")] broadcaster: Broadcaster::new(features_cache.clone()), }); let token_validator = Arc::new(TokenValidator { From a3da33cec86dccf23a1f07928a230eaeb13e73da Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 6 Dec 2024 14:16:36 +0100 Subject: [PATCH 33/55] only start polling if you're not streaming It seems that the only way to avoid firing off that job is to wrap the whole select in an if cfg! block. According to this issue, that's the best way https://github.com/tokio-rs/tokio/issues/3974 --- server/src/main.rs | 71 +++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index 1a14d0f6..811f8bbe 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -162,30 +162,55 @@ async fn main() -> Result<(), anyhow::Error> { let refresher = feature_refresher.clone().unwrap(); let validator = token_validator_schedule.clone().unwrap(); - tokio::select! { - _ = server.run() => { - tracing::info!("Actix is shutting down. Persisting data"); - clean_shutdown(persistence.clone(), lazy_feature_cache.clone(), lazy_token_cache.clone(), metrics_cache_clone.clone(), feature_refresher.clone()).await; - tracing::info!("Actix was shutdown properly"); - }, - // #[cfg(not(feature = "streaming"))] // fix this somehow - _ = refresher.start_refresh_features_background_task() => { - tracing::info!("Feature refresher unexpectedly shut down"); - } - _ = unleash_edge::http::background_send_metrics::send_metrics_task(metrics_cache_clone.clone(), refresher.clone(), edge.metrics_interval_seconds.try_into().unwrap()) => { - tracing::info!("Metrics poster unexpectedly shut down"); - } - _ = persist_data(persistence.clone(), lazy_token_cache.clone(), lazy_feature_cache.clone()) => { - tracing::info!("Persister was unexpectedly shut down"); - } - _ = validator.schedule_validation_of_known_tokens(edge.token_revalidation_interval_seconds) => { - tracing::info!("Token validator validation of known tokens was unexpectedly shut down"); - } - _ = validator.schedule_revalidation_of_startup_tokens(edge.tokens, lazy_feature_refresher) => { - tracing::info!("Token validator validation of startup tokens was unexpectedly shut down"); + + if cfg!(feature = "streaming") { + tokio::select! { + _ = server.run() => { + tracing::info!("Actix is shutting down. Persisting data"); + clean_shutdown(persistence.clone(), lazy_feature_cache.clone(), lazy_token_cache.clone(), metrics_cache_clone.clone(), feature_refresher.clone()).await; + tracing::info!("Actix was shutdown properly"); + }, + _ = unleash_edge::http::background_send_metrics::send_metrics_task(metrics_cache_clone.clone(), refresher.clone(), edge.metrics_interval_seconds.try_into().unwrap()) => { + tracing::info!("Metrics poster unexpectedly shut down"); + } + _ = persist_data(persistence.clone(), lazy_token_cache.clone(), lazy_feature_cache.clone()) => { + tracing::info!("Persister was unexpectedly shut down"); + } + _ = validator.schedule_validation_of_known_tokens(edge.token_revalidation_interval_seconds) => { + tracing::info!("Token validator validation of known tokens was unexpectedly shut down"); + } + _ = validator.schedule_revalidation_of_startup_tokens(edge.tokens, lazy_feature_refresher) => { + tracing::info!("Token validator validation of startup tokens was unexpectedly shut down"); + } + _ = metrics_pusher::prometheus_remote_write(prom_registry_for_write, edge.prometheus_remote_write_url, edge.prometheus_push_interval, edge.prometheus_username, edge.prometheus_password, app_name) => { + tracing::info!("Prometheus push unexpectedly shut down"); + } } - _ = metrics_pusher::prometheus_remote_write(prom_registry_for_write, edge.prometheus_remote_write_url, edge.prometheus_push_interval, edge.prometheus_username, edge.prometheus_password, app_name) => { - tracing::info!("Prometheus push unexpectedly shut down"); + } else { + tokio::select! { + _ = server.run() => { + tracing::info!("Actix is shutting down. Persisting data"); + clean_shutdown(persistence.clone(), lazy_feature_cache.clone(), lazy_token_cache.clone(), metrics_cache_clone.clone(), feature_refresher.clone()).await; + tracing::info!("Actix was shutdown properly"); + }, + _ = refresher.start_refresh_features_background_task() => { + tracing::info!("Feature refresher unexpectedly shut down"); + } + _ = unleash_edge::http::background_send_metrics::send_metrics_task(metrics_cache_clone.clone(), refresher.clone(), edge.metrics_interval_seconds.try_into().unwrap()) => { + tracing::info!("Metrics poster unexpectedly shut down"); + } + _ = persist_data(persistence.clone(), lazy_token_cache.clone(), lazy_feature_cache.clone()) => { + tracing::info!("Persister was unexpectedly shut down"); + } + _ = validator.schedule_validation_of_known_tokens(edge.token_revalidation_interval_seconds) => { + tracing::info!("Token validator validation of known tokens was unexpectedly shut down"); + } + _ = validator.schedule_revalidation_of_startup_tokens(edge.tokens, lazy_feature_refresher) => { + tracing::info!("Token validator validation of startup tokens was unexpectedly shut down"); + } + _ = metrics_pusher::prometheus_remote_write(prom_registry_for_write, edge.prometheus_remote_write_url, edge.prometheus_push_interval, edge.prometheus_username, edge.prometheus_password, app_name) => { + tracing::info!("Prometheus push unexpectedly shut down"); + } } } } From 3915279afcc71be700d032f1123bf0e7c14a1051 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 12:54:37 +0100 Subject: [PATCH 34/55] start switch to dashmap --- server/src/http/broadcaster.rs | 65 +++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 8cf4b1ea..61d2a01b 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -16,7 +16,6 @@ use actix_web_lab::{ }; use dashmap::DashMap; use futures_util::future; -use parking_lot::Mutex; use serde::Serialize; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; @@ -52,7 +51,8 @@ struct BroadcasterInner { } pub struct Broadcaster { - inner: Mutex, + active_connections: DashMap, + // inner: Mutex, features_cache: Arc>, } @@ -60,7 +60,8 @@ impl Broadcaster { /// Constructs new broadcaster and spawns ping loop. pub fn new(features: Arc>) -> Arc { let this = Arc::new(Broadcaster { - inner: Mutex::new(BroadcasterInner::default()), + // inner: Mutex::new(BroadcasterInner::default()), + active_connections: DashMap::new(), features_cache: features, }); @@ -84,22 +85,26 @@ impl Broadcaster { /// Removes all non-responsive clients from broadcast list. async fn remove_stale_clients(&self) { - let active_connections = self.inner.lock().active_connections.clone(); - - for (_query, mut group) in active_connections { - let mut ok_clients = Vec::new(); - - for client in group.clients { - if client - .send(sse::Event::Comment("keep-alive".into())) - .await - .is_ok() - { - ok_clients.push(client.clone()); - } - } - - group.clients = ok_clients; + // let active_connections = self.inner.lock().active_connections.clone(); + + for mut group in self.active_connections.iter_mut() { + // let (_query, &mut group) = entry.pair(); + let mut ok_clients = Vec::new(); + + for client in &group.clients { + if client + .send(sse::Event::Comment("keep-alive".into())) + .await + .is_ok() + { + ok_clients.push(client.clone()); + } + } + + // validate tokens here? + // ok_clients.iter().filter(|client| client.token_is_valid()) + + group.clients = ok_clients; } } @@ -118,9 +123,7 @@ impl Broadcaster { ) -> Sse>> { let (tx, rx) = mpsc::channel(10); - self.inner - .lock() - .active_connections + self.active_connections .entry(QueryWrapper { query: query.clone(), }) @@ -165,25 +168,29 @@ impl Broadcaster { /// Broadcast new features to all clients. pub async fn broadcast(&self) { - let active_connections = self.inner.lock().active_connections.clone(); - let mut client_events = Vec::new(); - for (_query, group) in active_connections { + for entry in self.active_connections.iter() { + let (_query, group) = entry.pair(); let filter_set = Broadcaster::get_query_filters(group.filter_set.clone(), group.token.clone()); let features = self .features_cache .get(&cache_key(&group.token)) .map(|client_features| filter_client_features(&client_features, &filter_set)); - let event: Event = sse::Data::new_json(&features).unwrap().event("unleash-updated").into(); + let event: Event = sse::Data::new_json(&features) + .unwrap() + .event("unleash-updated") + .into(); - for client in group.clients { - client_events.push((client, event.clone())); + for client in &group.clients { + client_events.push((client.clone(), event.clone())); } } // try to send to all clients, ignoring failures // disconnected clients will get swept up by `remove_stale_clients` - let send_events = client_events.iter().map(|(client, event)| client.send(event.clone())); + let send_events = client_events + .iter() + .map(|(client, event)| client.send(event.clone())); let _ = future::join_all(send_events).await; } From 730762573dc35f4eb0ad0a9cb4ae94f6e2b1eeca Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 12:55:59 +0100 Subject: [PATCH 35/55] remove comments and old comments --- server/src/http/broadcaster.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 61d2a01b..e9274f79 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -45,14 +45,8 @@ struct ClientGroup { token: EdgeToken, } -#[derive(Default)] -struct BroadcasterInner { - active_connections: HashMap, -} - pub struct Broadcaster { active_connections: DashMap, - // inner: Mutex, features_cache: Arc>, } @@ -60,7 +54,6 @@ impl Broadcaster { /// Constructs new broadcaster and spawns ping loop. pub fn new(features: Arc>) -> Arc { let this = Arc::new(Broadcaster { - // inner: Mutex::new(BroadcasterInner::default()), active_connections: DashMap::new(), features_cache: features, }); @@ -85,10 +78,7 @@ impl Broadcaster { /// Removes all non-responsive clients from broadcast list. async fn remove_stale_clients(&self) { - // let active_connections = self.inner.lock().active_connections.clone(); - for mut group in self.active_connections.iter_mut() { - // let (_query, &mut group) = entry.pair(); let mut ok_clients = Vec::new(); for client in &group.clients { @@ -146,9 +136,6 @@ impl Broadcaster { .unwrap(); Sse::from_infallible_receiver(rx) - // we're already using remove_stale_clients to clean up disconnected - // clients and send heartbeats. we probably don't need this. - // .with_keep_alive(Duration::from_secs(30)) } fn get_query_filters( From d29e10528ade84292e21970150bea31219637640 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 12:57:58 +0100 Subject: [PATCH 36/55] streaming-spike: remove parking lot from deps --- Cargo.lock | 1 - server/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f428e23..4cb018bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4372,7 +4372,6 @@ dependencies = [ "opentelemetry-prometheus", "opentelemetry-semantic-conventions", "opentelemetry_sdk", - "parking_lot", "prometheus", "prometheus-reqwest-remote-write", "prometheus-static-metric", diff --git a/server/Cargo.toml b/server/Cargo.toml index 814f2b40..a82478c9 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -61,7 +61,6 @@ opentelemetry_sdk = { version = "0.24.0", features = [ "serde_json", "logs", ] } -parking_lot = "0.12.3" prometheus = { version = "0.13.4", features = ["process"] } prometheus-reqwest-remote-write = { version = "0.2.1" } prometheus-static-metric = "0.5.1" From 7c855e66916d1d96bd0eb491f73a166e375ec49b Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 13:17:30 +0100 Subject: [PATCH 37/55] make new dependencies optional --- server/Cargo.toml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/server/Cargo.toml b/server/Cargo.toml index a82478c9..7fa59e5e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -15,7 +15,12 @@ repository = "https://github.com/Unleash/unleash-edge" version = "19.6.3" [features] -streaming = [] +streaming = [ + "actix-web-lab", + "eventsource-client", + "futures-util", + "tokio-stream", +] [package.metadata.wix] upgrade-guid = "11E5D83A-3034-48BB-9A84-9F589EBD648C" @@ -32,7 +37,7 @@ actix-http = "3.9.0" actix-middleware-etag = "0.4.2" actix-service = "2.0.2" actix-web = { version = "4.9.0", features = ["rustls-0_23", "compress-zstd"] } -actix-web-lab = "0.23.0" +actix-web-lab = { version = "0.23.0", optional = true } ahash = "0.8.11" anyhow = "1.0.91" async-trait = "0.1.83" @@ -44,10 +49,10 @@ cidr = "0.3.0" clap = { version = "4.5.19", features = ["derive", "env"] } clap-markdown = "0.1.4" dashmap = "6.0.1" -eventsource-client = "0.13.0" +eventsource-client = { version = "0.13.0", optional = true } futures = "0.3.30" futures-core = "0.3.30" -futures-util = "0.3.31" +futures-util = { version = "0.3.31", optional = true } iter_tools = "0.24.0" itertools = "0.13.0" lazy_static = "1.4.0" @@ -93,7 +98,7 @@ tokio = { version = "1.41.0", features = [ "tracing", "fs", ] } -tokio-stream = "0.1.16" +tokio-stream = { version = "0.1.16", optional = true } tracing = { version = "0.1.40", features = ["log"] } tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] } ulid = "1.1.2" From ed10879a00f6207e4241ca43adafd6dca898761c Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 13:21:26 +0100 Subject: [PATCH 38/55] return a client cache error if it isn't ready --- server/src/client_api.rs | 22 ++++++++++++---------- server/src/http/broadcaster.rs | 7 +++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 5d107d41..41ff4d28 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -59,16 +59,18 @@ pub async fn stream_features( ) .await; match (req.app_data::>(), features) { - (Some(refresher), Ok(features)) => Ok(refresher - .broadcaster - .connect( - validated_token, - filter_query.clone(), - query.clone(), - features, - ) - .await), - _ => todo!(), + (Some(refresher), Ok(features)) => { + refresher + .broadcaster + .connect( + validated_token, + filter_query.clone(), + query.clone(), + features, + ) + .await + } + _ => Err(EdgeError::ClientCacheError), } } diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index e9274f79..752e0cc5 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -1,6 +1,5 @@ /// copied from https://github.com/actix/examples/blob/master/server-sent-events/src/broadcast.rs use std::{ - collections::HashMap, hash::{Hash, Hasher}, sync::Arc, time::Duration, @@ -24,7 +23,7 @@ use unleash_types::client_features::{ClientFeatures, Query as FlagQuery}; use crate::{ filters::{filter_client_features, name_prefix_filter, project_filter, FeatureFilterSet}, tokens::cache_key, - types::{EdgeToken, FeatureFilters}, + types::{EdgeResult, EdgeToken, FeatureFilters}, }; #[derive(Debug, Clone, PartialEq, Eq, Serialize)] @@ -110,7 +109,7 @@ impl Broadcaster { filter_set: Query, query: unleash_types::client_features::Query, features: Json, - ) -> Sse>> { + ) -> EdgeResult>>> { let (tx, rx) = mpsc::channel(10); self.active_connections @@ -135,7 +134,7 @@ impl Broadcaster { .await .unwrap(); - Sse::from_infallible_receiver(rx) + Ok(Sse::from_infallible_receiver(rx)) } fn get_query_filters( From 7c03c5a29e211232fabe7739b58b88705e672a01 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 13:29:36 +0100 Subject: [PATCH 39/55] return result from connect --- server/src/http/broadcaster.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 752e0cc5..9755a65c 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -126,13 +126,11 @@ impl Broadcaster { }); tx.send( - sse::Data::new_json(features) - .unwrap() + sse::Data::new_json(features)? .event("unleash-connected") .into(), ) - .await - .unwrap(); + .await?; Ok(Sse::from_infallible_receiver(rx)) } From 36d6efdbaf586ba62f91c5ed245dd71927cb4e4c Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 13:59:51 +0100 Subject: [PATCH 40/55] stopgap --- server/src/client_api.rs | 20 ++---------- server/src/http/broadcaster.rs | 58 ++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 41ff4d28..f30b5e3f 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -43,31 +43,17 @@ pub async fn get_features( #[get("/streaming")] pub async fn stream_features( edge_token: EdgeToken, - features_cache: Data>, token_cache: Data>, filter_query: Query, req: HttpRequest, ) -> EdgeResult { let (validated_token, filter_set, query) = get_feature_filter(&edge_token, &token_cache, filter_query.clone())?; - let features = resolve_features_2( - query.clone(), - validated_token.clone(), - filter_set, - features_cache, - req.clone(), - ) - .await; - match (req.app_data::>(), features) { - (Some(refresher), Ok(features)) => { + match req.app_data::>() { + Some(refresher) => { refresher .broadcaster - .connect( - validated_token, - filter_query.clone(), - query.clone(), - features, - ) + .connect(validated_token, filter_query, query) .await } _ => Err(EdgeError::ClientCacheError), diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 9755a65c..69d2e9f2 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -23,7 +23,7 @@ use unleash_types::client_features::{ClientFeatures, Query as FlagQuery}; use crate::{ filters::{filter_client_features, name_prefix_filter, project_filter, FeatureFilterSet}, tokens::cache_key, - types::{EdgeResult, EdgeToken, FeatureFilters}, + types::{EdgeJsonResult, EdgeResult, EdgeToken, FeatureFilters}, }; #[derive(Debug, Clone, PartialEq, Eq, Serialize)] @@ -108,14 +108,22 @@ impl Broadcaster { token: EdgeToken, filter_set: Query, query: unleash_types::client_features::Query, - features: Json, ) -> EdgeResult>>> { let (tx, rx) = mpsc::channel(10); + let features = &self + .resolve_features(&token, filter_set.clone(), query.clone()) + .await?; + + tx.send( + sse::Data::new_json(features)? + .event("unleash-connected") + .into(), + ) + .await; + self.active_connections - .entry(QueryWrapper { - query: query.clone(), - }) + .entry(QueryWrapper { query }) .and_modify(|group| { group.clients.push(tx.clone()); }) @@ -125,19 +133,12 @@ impl Broadcaster { token, }); - tx.send( - sse::Data::new_json(features)? - .event("unleash-connected") - .into(), - ) - .await?; - Ok(Sse::from_infallible_receiver(rx)) } fn get_query_filters( filter_query: Query, - token: EdgeToken, + token: &EdgeToken, ) -> FeatureFilterSet { let query_filters = filter_query.into_inner(); @@ -146,17 +147,42 @@ impl Broadcaster { } else { FeatureFilterSet::default() } - .with_filter(project_filter(&token)); + .with_filter(project_filter(token)); filter_set } + async fn resolve_features( + &self, + validated_token: &EdgeToken, + filter_set: Query, + query: FlagQuery, + ) -> EdgeJsonResult { + let filter_set = Broadcaster::get_query_filters(filter_set.clone(), validated_token); + + let features = self + .features_cache + .get(&cache_key(validated_token)) + .map(|client_features| filter_client_features(&client_features, &filter_set)); + + match features { + Some(features) => Ok(Json(ClientFeatures { + query: Some(query), + ..features + })), + // Note: this is a simplification for now, using the following assumptions: + // 1. We'll only allow streaming in strict mode + // 2. We'll check whether the token is subsumed *before* trying to add it to the broadcaster + // If both of these are true, then we should never hit this case (if Thomas's understanding is correct). + None => todo!(), + } + } + /// Broadcast new features to all clients. pub async fn broadcast(&self) { let mut client_events = Vec::new(); for entry in self.active_connections.iter() { let (_query, group) = entry.pair(); - let filter_set = - Broadcaster::get_query_filters(group.filter_set.clone(), group.token.clone()); + let filter_set = Broadcaster::get_query_filters(group.filter_set.clone(), &group.token); let features = self .features_cache .get(&cache_key(&group.token)) From 481acce2eb314eb5da4d5e44a48d47dd2fc08bb0 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 14:07:47 +0100 Subject: [PATCH 41/55] remove futures_util --- Cargo.lock | 1 - server/Cargo.toml | 8 +------- server/src/http/broadcaster.rs | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cb018bc..636bd881 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4362,7 +4362,6 @@ dependencies = [ "eventsource-client", "futures", "futures-core", - "futures-util", "iter_tools", "itertools 0.13.0", "lazy_static", diff --git a/server/Cargo.toml b/server/Cargo.toml index 7fa59e5e..996ad04e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -15,12 +15,7 @@ repository = "https://github.com/Unleash/unleash-edge" version = "19.6.3" [features] -streaming = [ - "actix-web-lab", - "eventsource-client", - "futures-util", - "tokio-stream", -] +streaming = ["actix-web-lab", "eventsource-client", "tokio-stream"] [package.metadata.wix] upgrade-guid = "11E5D83A-3034-48BB-9A84-9F589EBD648C" @@ -52,7 +47,6 @@ dashmap = "6.0.1" eventsource-client = { version = "0.13.0", optional = true } futures = "0.3.30" futures-core = "0.3.30" -futures-util = { version = "0.3.31", optional = true } iter_tools = "0.24.0" itertools = "0.13.0" lazy_static = "1.4.0" diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 69d2e9f2..afc76f0e 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -14,7 +14,7 @@ use actix_web_lab::{ util::InfallibleStream, }; use dashmap::DashMap; -use futures_util::future; +use futures::future; use serde::Serialize; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; From 057f1742269cfc5a7913605da4abd0822fea3aa3 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 14:15:03 +0100 Subject: [PATCH 42/55] simplify some types --- server/src/client_api.rs | 35 +--------------------------------- server/src/error.rs | 12 ++++++++++++ server/src/http/broadcaster.rs | 2 +- 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index f30b5e3f..a3c4ea7e 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -47,7 +47,7 @@ pub async fn stream_features( filter_query: Query, req: HttpRequest, ) -> EdgeResult { - let (validated_token, filter_set, query) = + let (validated_token, _filter_set, query) = get_feature_filter(&edge_token, &token_cache, filter_query.clone())?; match req.app_data::>() { Some(refresher) => { @@ -83,9 +83,6 @@ pub async fn post_features( resolve_features(edge_token, features_cache, token_cache, filter_query, req).await } -/// this was extracted from resolve_features because it gives us the necessary -/// values to filter flags and construct client feature responses later. It's a -/// standalone function and can be moved to a different file if necessary. fn get_feature_filter( edge_token: &EdgeToken, token_cache: &Data>, @@ -119,36 +116,6 @@ fn get_feature_filter( Ok((validated_token, filter_set, query)) } -/// This is the second half of resolve_features. The idea was that you don't -/// need to do the extraction work twice. The broadcaster shold be able to do -/// something like the Some arm of the match here, except we'll know that we -/// already have the refresher. -#[cfg(feature = "streaming")] -async fn resolve_features_2( - query: unleash_types::client_features::Query, - validated_token: EdgeToken, - filter_set: FeatureFilterSet, - features_cache: Data>, - req: HttpRequest, -) -> EdgeJsonResult { - let client_features = match req.app_data::>() { - Some(refresher) => { - refresher - .features_for_filter(validated_token.clone(), &filter_set) - .await - } - None => features_cache - .get(&cache_key(&validated_token)) - .map(|client_features| filter_client_features(&client_features, &filter_set)) - .ok_or(EdgeError::ClientCacheError), - }?; - - Ok(Json(ClientFeatures { - query: Some(query), - ..client_features - })) -} - /// This is the same as it always was, except I extracted bits of it. async fn resolve_features( edge_token: EdgeToken, diff --git a/server/src/error.rs b/server/src/error.rs index 09b2652a..2bbefc9b 100644 --- a/server/src/error.rs +++ b/server/src/error.rs @@ -2,8 +2,12 @@ use std::error::Error; use std::fmt::{Display, Formatter}; use actix_web::{http::StatusCode, HttpResponseBuilder, ResponseError}; +#[cfg(feature = "streaming")] +use actix_web_lab::sse::Event; use serde::Serialize; use serde_json::json; +#[cfg(feature = "streaming")] +use tokio::sync::mpsc::error::SendError; use tracing::debug; use crate::types::{EdgeToken, Status, UnleashBadRequest}; @@ -291,5 +295,13 @@ impl From for EdgeError { } } +#[cfg(feature = "streaming")] +impl From> for EdgeError { + // todo: create better enum representation. use this is placeholder + fn from(_value: SendError) -> Self { + EdgeError::TlsError + } +} + #[cfg(test)] mod tests {} diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index afc76f0e..74856854 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -120,7 +120,7 @@ impl Broadcaster { .event("unleash-connected") .into(), ) - .await; + .await?; self.active_connections .entry(QueryWrapper { query }) From b98b859794c5abc963f2139b536d0d76410bfd54 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 14:15:51 +0100 Subject: [PATCH 43/55] bump log to info --- server/src/http/feature_refresher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index c9ba0717..a8e24d77 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -378,7 +378,7 @@ impl FeatureRefresher { refresher.broadcaster.broadcast().await; } eventsource_client::SSE::Event(event) => { - debug!( + info!( "Got an SSE event that I wasn't expecting: {:#?}", event ); From 5730e18ed7a14c1cdfdbbe4d7789cd0f2b62291d Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 14:31:20 +0100 Subject: [PATCH 44/55] add streaming url to urls --- server/src/http/feature_refresher.rs | 9 +++++---- server/src/urls.rs | 10 ++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index a8e24d77..774f7ae7 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -314,12 +314,15 @@ impl FeatureRefresher { /// This is where we set up a listener per token. #[cfg(feature = "streaming")] pub async fn start_streaming_features_background_task(&self) -> anyhow::Result<()> { + use anyhow::Context; + let refreshes = self.get_tokens_due_for_refresh(); for refresh in refreshes { let token = refresh.token.clone(); - let streaming_url = format!("{}/client/streaming", self.unleash_client.urls.api_url); + let streaming_url = self.unleash_client.urls.client_features_stream_url.as_str(); - let es_client = eventsource_client::ClientBuilder::for_url(&streaming_url)? + let es_client = eventsource_client::ClientBuilder::for_url(streaming_url) + .context("Failed to create EventSource client for streaming")? .header("Authorization", &token.token)? .reconnect( eventsource_client::ReconnectOptions::reconnect(true) @@ -332,7 +335,6 @@ impl FeatureRefresher { .build(); let refresher = self.clone(); - // let broadcaster = self.broadcaster.clone(); tokio::spawn(async move { let mut stream = es_client @@ -340,7 +342,6 @@ impl FeatureRefresher { .map_ok(move |sse| { let token = token.clone(); let refresher = refresher.clone(); - // let broadcaster = broadcaster.clone(); async move { match sse { // The first time we're connecting to Unleash. Just store the data. diff --git a/server/src/urls.rs b/server/src/urls.rs index 65b065da..fc9f5029 100644 --- a/server/src/urls.rs +++ b/server/src/urls.rs @@ -17,6 +17,8 @@ pub struct UnleashUrls { pub edge_validate_url: Url, pub edge_metrics_url: Url, pub new_api_token_url: Url, + #[cfg(feature = "streaming")] + pub client_features_stream_url: Url, } impl FromStr for UnleashUrls { @@ -49,6 +51,12 @@ impl UnleashUrls { .path_segments_mut() .unwrap() .push("features"); + #[cfg(feature = "streaming")] + let client_features_stream_url = client_api_url.clone(); + client_features_url + .path_segments_mut() + .unwrap() + .push("streaming"); let mut client_register_app_url = client_api_url.clone(); client_register_app_url .path_segments_mut() @@ -100,6 +108,8 @@ impl UnleashUrls { edge_validate_url, edge_metrics_url, new_api_token_url, + #[cfg(feature = "streaming")] + client_features_stream_url, } } } From d17c00047a9e078f3c8148bfdf435420c7a891a4 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 14:42:28 +0100 Subject: [PATCH 45/55] fix a url bug --- server/src/urls.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/urls.rs b/server/src/urls.rs index fc9f5029..0ae87209 100644 --- a/server/src/urls.rs +++ b/server/src/urls.rs @@ -52,8 +52,8 @@ impl UnleashUrls { .unwrap() .push("features"); #[cfg(feature = "streaming")] - let client_features_stream_url = client_api_url.clone(); - client_features_url + let mut client_features_stream_url = client_api_url.clone(); + client_features_stream_url .path_segments_mut() .unwrap() .push("streaming"); From 82aec30b6ce2464c4d63f6abaef4312ad9c2882b Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 14:44:29 +0100 Subject: [PATCH 46/55] move broadcasting into features updated --- server/src/http/feature_refresher.rs | 68 +++++++--------------------- 1 file changed, 17 insertions(+), 51 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 774f7ae7..2e13f51e 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -355,7 +355,7 @@ impl FeatureRefresher { // very rough handling of client features. let features: ClientFeatures = serde_json::from_str(&event.data).unwrap(); - refresher.handle_client_features_updated(TokenRefresh::new(token, None), features); + refresher.handle_client_features_updated(&token, features, None).await; } // Unleash has updated. This is where we send data to listeners. eventsource_client::SSE::Event(event) @@ -368,15 +368,7 @@ impl FeatureRefresher { // store the data locally let features: ClientFeatures = serde_json::from_str(&event.data).unwrap(); - refresher.handle_client_features_updated(TokenRefresh::new(token, None), features); - - // send the data to the broadcaster. This should probably just send the new - // feature set OR even just a "filter flags" - // function. The broadcaster will take care - // of filtering the flags per listener. - // let data = Data::new(event.data).event("unleash-updated"); - // broadcaster.rebroadcast(actix_web_lab::sse::Event::Data(data)).await; - refresher.broadcaster.broadcast().await; + refresher.handle_client_features_updated(&token, features, None).await; } eventsource_client::SSE::Event(event) => { info!( @@ -426,17 +418,19 @@ impl FeatureRefresher { } } - // this is a copy of the handling in refresh_single. Extracting just so we can handle the new flags in the same way without fetching them first. - #[cfg(feature = "streaming")] - fn handle_client_features_updated(&self, refresh: TokenRefresh, features: ClientFeatures) { - debug!("Handling client features update."); - let key = cache_key(&refresh.token); - let etag = refresh.etag; - self.update_last_refresh(&refresh.token, etag, features.features.len()); + async fn handle_client_features_updated( + &self, + refresh_token: &EdgeToken, + features: ClientFeatures, + etag: Option, + ) { + debug!("Got updated client features. Updating features with {etag:?}"); + let key = cache_key(refresh_token); + self.update_last_refresh(refresh_token, etag, features.features.len()); self.features_cache .entry(key.clone()) .and_modify(|existing_data| { - let updated_data = update_client_features(&refresh.token, existing_data, &features); + let updated_data = update_client_features(refresh_token, existing_data, &features); *existing_data = updated_data; }) .or_insert_with(|| features.clone()); @@ -462,6 +456,9 @@ impl FeatureRefresher { }; new_state }); + + #[cfg(feature = "streaming")] + self.broadcaster.broadcast().await; } pub async fn refresh_single(&self, refresh: TokenRefresh) { @@ -480,39 +477,8 @@ impl FeatureRefresher { self.update_last_check(&refresh.token.clone()); } ClientFeaturesResponse::Updated(features, etag) => { - debug!("Got updated client features. Updating features with {etag:?}"); - let key = cache_key(&refresh.token); - self.update_last_refresh(&refresh.token, etag, features.features.len()); - self.features_cache - .entry(key.clone()) - .and_modify(|existing_data| { - let updated_data = - update_client_features(&refresh.token, existing_data, &features); - *existing_data = updated_data; - }) - .or_insert_with(|| features.clone()); - self.engine_cache - .entry(key.clone()) - .and_modify(|engine| { - if let Some(f) = self.features_cache.get(&key) { - let mut new_state = EngineState::default(); - let warnings = new_state.take_state(f.clone()); - if let Some(warnings) = warnings { - warn!("The following toggle failed to compile and will be defaulted to off: {warnings:?}"); - }; - *engine = new_state; - - } - }) - .or_insert_with(|| { - let mut new_state = EngineState::default(); - - let warnings = new_state.take_state(features); - if let Some(warnings) = warnings { - warn!("The following toggle failed to compile and will be defaulted to off: {warnings:?}"); - }; - new_state - }); + self.handle_client_features_updated(&refresh.token, features, etag) + .await } }, Err(e) => { From 73b2c0790c9c9bd1cdc6bb6b4caa8395d6a86fdc Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 14:51:31 +0100 Subject: [PATCH 47/55] don't unwrap serialization --- server/src/http/feature_refresher.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 2e13f51e..9f278fb1 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -344,31 +344,33 @@ impl FeatureRefresher { let refresher = refresher.clone(); async move { match sse { - // The first time we're connecting to Unleash. Just store the data. + // The first time we're connecting to Unleash. eventsource_client::SSE::Event(event) if event.event_type == "unleash-connected" => { debug!( - "Connected to unleash! I should populate my flag cache now.", + "Connected to unleash! Populating my flag cache now.", ); - // very rough handling of client features. - let features: ClientFeatures = - serde_json::from_str(&event.data).unwrap(); - refresher.handle_client_features_updated(&token, features, None).await; + match serde_json::from_str(&event.data) { + Ok(features) => { refresher.handle_client_features_updated(&token, features, None).await; } + Err(_) => { warn!("Could not parse features response to internal representation"); + } + } } - // Unleash has updated. This is where we send data to listeners. + // Unleash has updated features for us. eventsource_client::SSE::Event(event) if event.event_type == "unleash-updated" => { debug!( - "Got an unleash updated event. I should update my cache and notify listeners.", + "Got an unleash updated event. Updating cache.", ); - // store the data locally - let features: ClientFeatures = - serde_json::from_str(&event.data).unwrap(); - refresher.handle_client_features_updated(&token, features, None).await; + match serde_json::from_str(&event.data) { + Ok(features) => { refresher.handle_client_features_updated(&token, features, None).await; } + Err(_) => { warn!("Could not parse features response to internal representation"); + } + } } eventsource_client::SSE::Event(event) => { info!( From 606ec0d8e318b96d93414caa195e3e3307c98e04 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 14:54:30 +0100 Subject: [PATCH 48/55] print some kind of error message --- server/src/http/feature_refresher.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 9f278fb1..b6669015 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -354,7 +354,7 @@ impl FeatureRefresher { match serde_json::from_str(&event.data) { Ok(features) => { refresher.handle_client_features_updated(&token, features, None).await; } - Err(_) => { warn!("Could not parse features response to internal representation"); + Err(e) => { warn!("Could not parse features response to internal representation: {e:?}"); } } } @@ -368,7 +368,7 @@ impl FeatureRefresher { match serde_json::from_str(&event.data) { Ok(features) => { refresher.handle_client_features_updated(&token, features, None).await; } - Err(_) => { warn!("Could not parse features response to internal representation"); + Err(e) => { warn!("Could not parse features response to internal representation: {e:?}"); } } } From 4c151c90c5a951f4fc112e4399c8e618858c3b31 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 11 Dec 2024 15:02:28 +0100 Subject: [PATCH 49/55] remove comment --- server/src/client_api.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/client_api.rs b/server/src/client_api.rs index a3c4ea7e..9806e846 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -116,7 +116,6 @@ fn get_feature_filter( Ok((validated_token, filter_set, query)) } -/// This is the same as it always was, except I extracted bits of it. async fn resolve_features( edge_token: EdgeToken, features_cache: Data>, From e6645d57758f0198fab9cdb4ca27caa68935f7aa Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 12 Dec 2024 09:35:19 +0100 Subject: [PATCH 50/55] Update server/src/http/broadcaster.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nuno Góis --- server/src/http/broadcaster.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 74856854..66f73a87 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -1,4 +1,3 @@ -/// copied from https://github.com/actix/examples/blob/master/server-sent-events/src/broadcast.rs use std::{ hash::{Hash, Hasher}, sync::Arc, From 81aa0e18e2d938c79d2bb0cd196d14912da09ac3 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 12 Dec 2024 09:45:39 +0100 Subject: [PATCH 51/55] comments from PR --- server/src/http/broadcaster.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 66f73a87..e904a399 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -20,6 +20,7 @@ use tokio_stream::wrappers::ReceiverStream; use unleash_types::client_features::{ClientFeatures, Query as FlagQuery}; use crate::{ + error::EdgeError, filters::{filter_client_features, name_prefix_filter, project_filter, FeatureFilterSet}, tokens::cache_key, types::{EdgeJsonResult, EdgeResult, EdgeToken, FeatureFilters}, @@ -56,26 +57,26 @@ impl Broadcaster { features_cache: features, }); - Broadcaster::spawn_ping(Arc::clone(&this)); + Broadcaster::spawn_heartbeat(Arc::clone(&this)); this } /// Pings clients every 30 seconds to see if they are alive and remove them from the broadcast /// list if not. - fn spawn_ping(this: Arc) { + fn spawn_heartbeat(this: Arc) { tokio::spawn(async move { let mut interval = interval(Duration::from_secs(30)); loop { interval.tick().await; - this.remove_stale_clients().await; + this.heartbeat().await; } }); } /// Removes all non-responsive clients from broadcast list. - async fn remove_stale_clients(&self) { + async fn heartbeat(&self) { for mut group in self.active_connections.iter_mut() { let mut ok_clients = Vec::new(); @@ -97,11 +98,6 @@ impl Broadcaster { } /// Registers client with broadcaster, returning an SSE response body. - /// The current impl takes the feature set as input and sends it to the client as a connected event. - /// - /// The commented-out arguments are what we'll need to store per client so - /// that we can properly filter / format the feature response when they get - /// updates later. pub async fn connect( &self, token: EdgeToken, @@ -172,7 +168,7 @@ impl Broadcaster { // 1. We'll only allow streaming in strict mode // 2. We'll check whether the token is subsumed *before* trying to add it to the broadcaster // If both of these are true, then we should never hit this case (if Thomas's understanding is correct). - None => todo!(), + None => Err(EdgeError::ClientCacheError), } } @@ -182,6 +178,7 @@ impl Broadcaster { for entry in self.active_connections.iter() { let (_query, group) = entry.pair(); let filter_set = Broadcaster::get_query_filters(group.filter_set.clone(), &group.token); + let features = self .features_cache .get(&cache_key(&group.token)) From f55bef8589a4fed7616011478dc95b5d358d7d67 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 12 Dec 2024 09:59:43 +0100 Subject: [PATCH 52/55] use resolve_features --- server/src/http/broadcaster.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index e904a399..ab38a144 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -17,6 +17,7 @@ use futures::future; use serde::Serialize; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; +use tracing::warn; use unleash_types::client_features::{ClientFeatures, Query as FlagQuery}; use crate::{ @@ -176,21 +177,22 @@ impl Broadcaster { pub async fn broadcast(&self) { let mut client_events = Vec::new(); for entry in self.active_connections.iter() { - let (_query, group) = entry.pair(); - let filter_set = Broadcaster::get_query_filters(group.filter_set.clone(), &group.token); - - let features = self - .features_cache - .get(&cache_key(&group.token)) - .map(|client_features| filter_client_features(&client_features, &filter_set)); - let event: Event = sse::Data::new_json(&features) - .unwrap() - .event("unleash-updated") - .into(); - - for client in &group.clients { - client_events.push((client.clone(), event.clone())); - } + let (query, group) = entry.pair(); + + let _ = self + .resolve_features(&group.token, group.filter_set.clone(), query.query.clone()) + .await + .and_then(|features| sse::Data::new_json(features).map_err(|e| e.into())) + .map(|sse_data| { + let event: Event = sse_data.event("unleash-updated").into(); + + for client in &group.clients { + client_events.push((client.clone(), event.clone())); + } + }) + .map_err(|e| { + warn!("Failed to broadcast features: {:?}", e); + }); } // try to send to all clients, ignoring failures // disconnected clients will get swept up by `remove_stale_clients` From b4e72fb747d0be4454e1f5f96c9282214b3ad3ca Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 12 Dec 2024 10:01:21 +0100 Subject: [PATCH 53/55] use a match instead --- server/src/http/broadcaster.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index ab38a144..9c441f72 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -179,20 +179,23 @@ impl Broadcaster { for entry in self.active_connections.iter() { let (query, group) = entry.pair(); - let _ = self + let event_data = self .resolve_features(&group.token, group.filter_set.clone(), query.query.clone()) .await - .and_then(|features| sse::Data::new_json(features).map_err(|e| e.into())) - .map(|sse_data| { + .and_then(|features| sse::Data::new_json(features).map_err(|e| e.into())); + + match event_data { + Ok(sse_data) => { let event: Event = sse_data.event("unleash-updated").into(); for client in &group.clients { client_events.push((client.clone(), event.clone())); } - }) - .map_err(|e| { + } + Err(e) => { warn!("Failed to broadcast features: {:?}", e); - }); + } + } } // try to send to all clients, ignoring failures // disconnected clients will get swept up by `remove_stale_clients` From 2c66bc643f324688f6db70688c0c23eb30f226a6 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 12 Dec 2024 10:07:36 +0100 Subject: [PATCH 54/55] use a reference for creating data --- server/src/http/broadcaster.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/http/broadcaster.rs b/server/src/http/broadcaster.rs index 9c441f72..0b4a4250 100644 --- a/server/src/http/broadcaster.rs +++ b/server/src/http/broadcaster.rs @@ -182,7 +182,7 @@ impl Broadcaster { let event_data = self .resolve_features(&group.token, group.filter_set.clone(), query.query.clone()) .await - .and_then(|features| sse::Data::new_json(features).map_err(|e| e.into())); + .and_then(|features| sse::Data::new_json(&features).map_err(|e| e.into())); match event_data { Ok(sse_data) => { From b54158abf170da0af1c9c207b10cb1d61c7507d2 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 12 Dec 2024 10:08:40 +0100 Subject: [PATCH 55/55] move broadcaster to not clone --- server/src/http/feature_refresher.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index b6669015..3d6e526e 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -164,14 +164,14 @@ impl FeatureRefresher { FeatureRefresher { unleash_client, tokens_to_refresh: Arc::new(DashMap::default()), - features_cache: features.clone(), + #[cfg(feature = "streaming")] + broadcaster: Broadcaster::new(features.clone()), + features_cache: features, engine_cache: engines, refresh_interval: features_refresh_interval, persistence, strict, app_name: app_name.into(), - #[cfg(feature = "streaming")] - broadcaster: Broadcaster::new(features.clone()), } }