0.34 #2

Merged
gru merged 16 commits from 0.34 into master 2026-10-18 12:01:51 +02:00
38 changed files with 899 additions and 238 deletions
Showing only changes of commit 434e789144 - Show all commits

No files matched your search

+23
View File
@@ -5,6 +5,29 @@
## 0.34.0
- Follow-up HOT cache path: clone response headers outside the shared store
mutex using immutable `Arc<HeaderMap>` metadata; preserve exact hit/LRU
accounting and per-response Age/transform flags. Fast-path direct no-Vary
keys and avoid per-HIT domain string allocations, inactive BAN locks and
repeated full retention sweeps (stale objects remain for revalidation).
- Add dedicated concurrent HOT hit, transformed header isolation, exact LRU,
BAN, stale-age tests and a manually ignored microbenchmark. No policy,
cache-file format or panel changes. New code remains uncompiled/unmeasured;
see docs/CACHE_HOTPATH_0.34.0.md.
- Follow-up reverse-proxy RPS revision: reuse typed upstream HTTP origin URIs,
use thread-local cryptographic nonce generation for per-request IDs,
reduce smooth-WRR member-ID clones, and share compiled health policies with probes.
- Cache backend lease once for streaming uploads instead of locking it on each
DATA frame; move access-log events directly into history when there are no
external consumers; remove repeated route attribution in forwarding.
- Retain exact per-request pool-recycle count and age limits plus independent
supervisor rotation, and keep H1/H2 protocol telemetry. No altered upstream keepalive defaults or cache query policies.
- Add source-only invariants, Rust regression cases and reproducible live
before/after/nginx benchmarking instructions. This revision has not yet been
compiled or measured for RPS; see docs/PROXY_RPS_0.34.0.md.
- Cache regression fix: interpret local max TTL as a cap on remaining freshness at receipt, not on the upstream Age clock. Preserve RFC 9111 Age and transformed response validators; prohibit private/no-store caching.
- HOT hit optimization: update LRU and hit counts in place, without removing/re-indexing the complete cache entry and its Vary variants on every hit.
- Expose both total HTTP Age and time resident in RustProxy HOT/COLD in the cache dashboard, with Polish/English labels.
+13
View File
@@ -1,5 +1,18 @@
# RustProxy 0.34.0
## 0.34.0: HOT HIT critical-section follow-up (source candidate)
Immutable response headers are shared until a HIT; the mutable per-response
copy and Body are built outside the cache's shared HOT mutex. No-Vary lookups,
per-domain counters, repeated cache reasons and inactive BAN checks use
shorter paths. The existing stale-for-revalidation policy makes the previous
full HOT sweep unnecessary. Exact LRU, generation barriers, BAN, COLD,
transformation/ETag, TTL and cache bypass settings remain unchanged.
This is **uncompiled source**; do not infer network RPS or production acceptance
from source checks. See
[`docs/CACHE_HOTPATH_0.34.0.md`](docs/CACHE_HOTPATH_0.34.0.md).
## 0.34.0: reverse-proxy hot-path optimization revision (source candidate)
This revision retains the **0.34.0** version and all prior scoped-cache,
+84 -82
View File
@@ -2,7 +2,7 @@
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
46bab1d2c2d6e0a80d57a6b232b1ba4c220884b95cd45ddccbafce2aa160cddb AGENTS.md
4e0a7371f060063a6a73ae1c1a2faba2de8d227118be2406c88ca432c90305eb CHANGELOG.md
688f666a676d77847aade6ef97fd82a565b88c9b314050ddeb313a1b3ec4663a CHANGELOG.md
ca201e3cd3f9842eae0006d0b40a0a72bcd3d4da6e4e5ecaf200d1b9a1390db2 Cargo.lock
fec5f1bae9a59ef1fabd27c5e45d31448dd8f811736eebfa8826b3806a7e3349 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
@@ -56,6 +56,7 @@ e68d5a5cf0991de9308f78afb0264e02979f7463a5f395eb7a6e9ec2bd3a3d54 docs/PHP_FPM.m
bc068a176056f5ea4d3b637071e644ed58a2140494c233ef22aa66e9700e3299 docs/PHP_FPM_ISOLATION.md
ad6e3f003b820a4f45e30e39577d120a69d167c70910bda4891971759458c3dd docs/PROMETHEUS.md
deea89aed410c0619bdbdb12437edc7aff9dca2fec7fdc059e918c649fc36b51 docs/PROXY_HOTPATH_0.34.0.md
f92715fc95ca3b837ff8352e3c899569cdb0f72bcb45c09b050cf1f1ff7ccfe5 docs/PROXY_RPS_0.34.0.md
887d1fdcc3b546b024afcd18b1716b8e3a74a3800012bf4bda7a122d2eabe233 docs/README.md
6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md
0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md
@@ -69,7 +70,7 @@ ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.
4dcf3243a13d9ae1827b5cefdbb9e27bd6310321c50d4d5934527d44437df6ef docs/SETTINGS.md
8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md
95148bfa1d2f8ff52b591c9acefc6c8992dadc4ebda43a195101d9b31d67a576 docs/TEST_REPORT.md
4c82db7fab8c8fe8cd1508fbb85751b7f72071edd913ee7def4a9ffde8efa13e docs/TEST_REPORT_0.34.0.md
ec1bf1690edc056068cbdec7244e3a98a2da2da953e0b4399301d76585a69be6 docs/TEST_REPORT_0.34.0.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md
@@ -129,7 +130,7 @@ a641bd35400c56f3b68b0a92e97b602d06090f03972ca8f01fa744e90dd97403 scripts/deploy
2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migration/migrate_npm.py
a459d95bbb0076f33ade8b273b813308167f9d8d45242b309a2d18320ca0093c scripts/migration/migrate_npm_api.py
31ccad3ec3c880119f3b7002f0c6652f8b43634edad8ab3763cb0e4497d0bb1f scripts/migration/migrate_npm_core.py
e4fd7fa94d661d4610d7d86fcdeaae9d6deb220e95b8d3bbd67ec15fbd017ccb scripts/performance/README.md
c79b709fb6f806a772464b709e652e6dfcf498b850281df5e1c594c98392cb23 scripts/performance/README.md
0380406a059ee9ec27473f8a46435f638f73a6dc729382ae1561c08dbf7ccb53 scripts/performance/benchmark.py
9ac220163256e08513597c397c016a48345b114d7876b439259cc310b36c83fc scripts/performance/checks_cache.py
f09125d081d0eed9a44fdca75de14e6bbf09dcb00fe1f027ba2270f89e3ff192 scripts/performance/checks_policy.py
@@ -157,8 +158,8 @@ e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/runtim
046b4282550f5cb4e53906665c91c7442be70fc68fd0743466fd832582f7c979 scripts/test_stream_tcp.py
f0b48ec0cc4411755cc0b4128eebce710375afa99344cb76198cf31074f8eb63 scripts/test_stream_udp.py
fafd577ccbb58485c47de35fc125e5c64a3a7628ae40d01d4cd924fce2f414db scripts/tests/cache_age_policy_034_model.py
972d79127309071a15e231ca45d24c9a8347c25115d0ec4fd91a74ca56b3f665 scripts/tests/cache_age_transform_034_live.py
955c150f8d0490069cb14b659d4fb4e0b529a28b87c27bd6697935c23e7ef192 scripts/tests/cache_scope_034_static_test.py
b75a381d1b41bfb2ea7d2ce09728249b5e1465f76d57e2c3f151af72a5ae4d03 scripts/tests/cache_age_transform_034_live.py
6cb79535f742d1a41685f486688dc2f1330ea362e3da1a15d4fa32561094282f scripts/tests/cache_scope_034_static_test.py
efb7a1fde2c74f30cd4a1abb88ae07c1cac7378bb33685dd09d52bb30849daac scripts/tests/dashboard_chart_headers_0335_browser_test.py
eb2c9e5c968938b6fbe4675b547ba1f537519878b9095e1fea133f4d83f96fc8 scripts/tests/dashboard_chart_typography_0337_browser_test.py
6a908fd434d2a8a80adfd06a66d4b83c4174e448d453a8c10b1812475d036677 scripts/tests/dashboard_chart_width_0333_browser_test.py
@@ -178,6 +179,7 @@ fa42ec0475e4398d647b3c094309b4332cd2b5649db223cae0b8e821d6b44a28 scripts/tests/
b0136544af717b75df2cb107900ea65c3f945fdd0040369dbb93919008fb954c scripts/tests/panel_html.py
8d1903e2b17b3d59cd0a5bec3e298e468b3d88ed2dda8b724239cc45d8353c59 scripts/tests/progressbar_0332_browser_test.py
f94612eaefb4098e1a4efc0d970ce52a656db239aaa0a012df47567527bf3708 scripts/tests/proxy_hotpath_034_static_test.py
9d28d523aeed722f6ed9217d6eb30530d4fb103e036983d680909a9011dba8de scripts/tests/rps_hotpath_034_static_test.py
5be5cdc60324f4f0f6638640000b888a5babb0a9bbf29511c824a0ba38878c2a scripts/tests/sendfile_034_panel_browser_test.py
978789bfe89d43034dfebddba49b9b17daedb1be031732734f95375e56c78306 scripts/tests/sendfile_034_static_test.py
1bb99b148d0297235b707f16865ca077329246fc19ecbb20a143bdeb56a23f37 scripts/tests/sidebar_nav_0333_browser_test.py
@@ -191,26 +193,26 @@ f0a2e3daa0a49aac54723cb5a281360e49a97c076281372c5cca2a85e096c02c scripts/tests/
f55efa99245bbdbc98fe3202e52952d224ac3200612561580d52f6e251810049 scripts/tests/web_server_0330_ui_test.py
0dd1cef06a1c3e1546e2fb9b085bf369dfbed02107b9ab01b4fb4f3b894099c0 scripts/tests/web_server_0334_browser_test.py
22e880e98c7b9d700c7a4c02b9b1d543e5dbd1f4ecdca42813a1083165b32a19 scripts/version.py
55485a513e8c4da2a58daa7abcb15b32ed408a5fc062cf23599fb83d16b90cfe src/acl.rs
63efab1e95d41134fc30cc57320c32810bbae9f81a640bdb24b1be2059e63d8a src/acl/dynamic.rs
4803e44de39b872a5b3b55f9ca9e81c066ba52cb8f6adb879fd57c5f94b93e89 src/acl/dynamic/tests.rs
63efab1e95d41134fc30cc57320c32810bbae9f81a640bdb24b1be2059e63d8a src/acl/dynamic.rs
a22e5ea6381a1eacafe650be0f5cd6e653773e31f6491a562fe0459a309fb7cc src/acl/tests.rs
6f5d3137bf24451da05c1db9f0f9e2a451305db17b6bc2590815ad54a6511829 src/acme.rs
55485a513e8c4da2a58daa7abcb15b32ed408a5fc062cf23599fb83d16b90cfe src/acl.rs
587097620b2f25ce7c6f59f907b47660184b095e3040c7615e74509a82e7cdd8 src/acme/jobs.rs
3f158f46e80b403b658b4e9b1e3710e38c70e47b2d476b8223d9c394ffeeba95 src/acme/model.rs
c5334de6c732d76b2bea7bfc17b3ac305a250bbaad31a5420d6a7d7abb200449 src/acme/providers.rs
d1dcb7bdfb7cfdc9c3b88cfb6908201c58532bc888744a13f45853079f214659 src/acme/providers/tests.rs
c5334de6c732d76b2bea7bfc17b3ac305a250bbaad31a5420d6a7d7abb200449 src/acme/providers.rs
29ff0647417b0f1d50a4cc075ab51afecff9ddf47db3eb7948d51db5a38f33f3 src/acme/runtime_bootstrap.rs
87f64198770ae026c03a242fa542db2590ca734b02e756f1e5c28d66236f753a src/acme/runtime_env.rs
f8075a5ef9ae5651d72a33ac0f0fd5b44ad0ae30f6e2d2318412177e0ed837f5 src/acme/runtime_versions.rs
e38331a1b186b49fe9d04051de52b603e1fa028def37e4e2f52e386b4d7b29ac src/acme/tests.rs
6f5d3137bf24451da05c1db9f0f9e2a451305db17b6bc2590815ad54a6511829 src/acme.rs
b6f38f3fd75d6685bbc882ed1077a38bf495cfcf01c397f69ed9b98be68b446f src/admin/access.rs
b4a2a068acfa1143b0032b9cd072c19cf3fdda2580f0b932d11d6368cd2ab3ad src/admin/certificate_api.rs
51f46a2cb3167c4e3a784dfbb59b8fe0e9d5d3d51365b26699498e31d0afb6a9 src/admin/certificates.rs
b5f6d49f1add583cfc8cd4aa36ffe4db56dac4de3b151fe8239fe42f507019cc src/admin/dashboard_presets.rs
5e150d4ef0413a2f92db5b6631669933b84d4b4796633d01476c4b39188cd05c src/admin/documentation.rs
958546c43c05bf4bed8121b24db3b3096f9243f3c56a71e1762592b755684013 src/admin/documentation/documents.rs
67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs
5e150d4ef0413a2f92db5b6631669933b84d4b4796633d01476c4b39188cd05c src/admin/documentation.rs
1ce779b84aa8f47bdb3c34f83593c1f2f06d2235f0319847096cfcd035434168 src/admin/file_log_search.rs
ab18acf6fb4ac86fb22635b9219a12dac76d24952c8f15538486267a9b4b7855 src/admin/file_logs.rs
c6b552cd5acd9e743c28b1a5564420c214c619fe891ab0561b7d4499aae8d6c0 src/admin/file_logs_io.rs
@@ -224,8 +226,8 @@ d9120f7dc61d27621c5ac27ea49815dc7945c44d5b99d208da503818c198f649 src/admin/oper
92b3e05f04711d8001b56816405859631d9e917868e15be965c7e76ef4ddef99 src/admin/preferences.rs
4a5cb4c3a3aa8b9a519020854506ce4be5e688a6c4298260322ae641dded7fe9 src/admin/preferences_columns.rs
9a3f13e2477769ae680c03d63336880c3b7b7d5aba4a91e44390a976608b9b1d src/admin/preferences_tests.rs
4a7a2017ba57c9e7a8b29e73764d439f6d03653403909ef198ded8c48d152784 src/admin/public.rs
7a00e4ab943f74d2b1bdcbfab7f9cd3f584e9f59536b4aca6c4c31f5f5508d12 src/admin/public/tests.rs
4a7a2017ba57c9e7a8b29e73764d439f6d03653403909ef198ded8c48d152784 src/admin/public.rs
9926827131254677a848c178c80850b3ca1993c05b4bf913687dc0c7992ad048 src/admin/refresh_preferences.rs
daef5e663c1bc99a22b3b8fa4481ae5e4916b91db132c104bc050f51b1fdf77f src/admin/router.rs
a40adfbaa908da3ea726d7aa3d45394a70a6a55b6fc0063270ec4d2a2f2026a3 src/admin/session.rs
@@ -302,45 +304,44 @@ b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e src/admin/web/
bb93e2017f40e3a45ff7f4ca92e4188bb464f432704b6d2c39da9d9621b7984c src/admin/web/vhosts/render.js
4dfdeb7c73c2775eacaca65fd8ef1dc80b275421b15522d319d338f5b96288cc src/admin/web/web-server-profiles.js
fd476445a88eb40270e706814eee50cbfbba4069a26dd843c095367e55a147a1 src/admin/web/web-server.js
53237ae8b08d261ce21e0aa6fd225bf4f3bb17a37a80529e2a4039d45b13f7c5 src/auth/files.rs
b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files/tests.rs
53237ae8b08d261ce21e0aa6fd225bf4f3bb17a37a80529e2a4039d45b13f7c5 src/auth/files.rs
775178ee5781e64574bdd7c1fb7d64e7e283977fbd9c98c99896532a7f1df612 src/auth/mod.rs
c96a87e82ff0c1c9ce3d0cd5135903bb53af78d1c4fb6ebfe5fea503bf5644ea src/auth/store.rs
745226cabb007819370fc6aa680995a03a8a13fa057dacfab0fcc21d0c43bbe4 src/auth/tests.rs
09c3a622a7d2169e49e3369ea5f0f816eab2f717eaf1675c625d518c8f34d367 src/backup.rs
76aa2afe9e7302be39b3512f30d0448d9134300f8ae930d64ada40cf3ad4a8a3 src/backup/archive.rs
732cd4f9ed38ae69f8bbae61b1db8baaa6f86b07ca21c41f24a206a30f0a96fc src/backup/ownership.rs
076350506215faec3cfe7d9de4e60baf29672cc22a41eabe601dcc510c257799 src/backup/snapshot.rs
5c19490d834d9971166b6b784a75b081bb95fd50c8c634ef3c87924dfffd4a19 src/backup/tests.rs
f5afae218f3a6f91bc2f1f5a90094630ce5b7ca574de61807b5a317b13937ec2 src/backup/validation.rs
426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs
09c3a622a7d2169e49e3369ea5f0f816eab2f717eaf1675c625d518c8f34d367 src/backup.rs
ca1e03b56a872dca37ed9d05ead41c1538a808e80b09fb9494823c443cd51c61 src/balance/health.rs
59d90e709bfc2be6cd48576f1c1d222891523853a46f2561f0562d74d01f60a5 src/balance/lease_runtime.rs
109c55f9af8918d4f314f0796e19de06f3129eefee3ef405770b99fc368f2aee src/balance/lease_runtime.rs
554b730c39a935b7da46bfb76b474b99cee07f8c482cb0490f2cf81ba7a51cf2 src/balance/model.rs
9a10649c02eeb2c05e0ca520ef6ab701f79d46591190b4432977c2d5dbc6d7f5 src/balance/pool_core.rs
8550e5d64aa6fee442cea3e8e243fc4cfc1e8aa39726bc5cc6b2afa9307e3d05 src/balance/pool_core.rs
477132dd381e23f1be197c32ff58a0925f41d3e6aa63fe77c10973cefb2b76f6 src/balance/pool_selection.rs
8abb6048303ee8c022546a528d5693167766e7450efd8e20f37ec6fd0fa9ef5e src/balance/pool_view.rs
842318f21a919055522f8bd9dae67bf4ec1c8c32dc52314cc048ba1d54a0b379 src/balance/tests.rs
5049d2bd01a9c866f3bad2288d8d594df96bba6045dfe15cd22b4173562b7388 src/basic_auth.rs
426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs
9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs
7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs
5049d2bd01a9c866f3bad2288d8d594df96bba6045dfe15cd22b4173562b7388 src/basic_auth.rs
a9eeb93519aae2de7a1282296ced91d2eb8307cc966f8fbf6085af0fd21b0ad2 src/cache/cold/cleanup.rs
8354ae90c1a0857773a0c6d79427925e0115d94ccfafb809ccc1ebf4ef10f9b9 src/cache/cold/demotion.rs
2eaba3261da2a547e9af91330319a3abd3a8ad8b5b44a337a84040720209017d src/cache/cold/startup.rs
e7c7fd71727d542c2df3e03a4a1cba9d839c516963f66bbfde1c29ec81053500 src/cache/cold/worker.rs
7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs
d51fa4f320be56c572f767617428c3f3838543cb0eeb7d1f8ae791e1a1ce9a71 src/cache/collapse.rs
7e8741bc21e5ca75f3bf8c5e57c59b6814dc1e5e2e0548963fe35089256772bb src/cache/dashboard.rs
780c2654c67dbe176d8310678e6deec8022bcebfd85b95df7262069ed41f57b1 src/cache/fill.rs
675d18afb53f25c56cba49bbb431fa39ecd9d7bdd192c7b39b0698225de7a2a2 src/cache/invalidation.rs
c1357a13658e1ed5158084d3a41ba9ea906512774a5863ce9c56cb508d3fa6c8 src/cache/invalidation/ban.rs
0cc31d8e70fb3774c4ce3713b94ed79c184a8d8f0d38ce66a97724410733e17e src/cache/invalidation/invalidate.rs
675d18afb53f25c56cba49bbb431fa39ecd9d7bdd192c7b39b0698225de7a2a2 src/cache/invalidation.rs
373031e18d0c23d006a31822ef977015c0263637e3da70c1442407f7bfed1033 src/cache/lifecycle.rs
bd831807ca4720b3a41a6ea1d9a6c0ed2d935225e2dacbd9313be064f2e4a20e src/cache/lookup.rs
5aa0d9ae53f0350f5cfc4d1694a6faf9eea893460d421344d3bc5d1c4ce66529 src/cache/lookup/hot.rs
1779abcee77600c13050a2882f896a5b35625b1ae53737c2554179492ce0e40d src/cache/lookup/insert.rs
107a589974b9689009a9bbf088818dfaec71cd1aab8676bbc6d0118359a54a31 src/cache/lookup/tiered.rs
bd831807ca4720b3a41a6ea1d9a6c0ed2d935225e2dacbd9313be064f2e4a20e src/cache/lookup.rs
58c03fde09e5eab40968679793b364f4f1cf5fd832bd79ebcdc92bb23871ad82 src/cache/mod.rs
0c8b0d550e94d4474dcdf991aa0340228dc5f2f904f39350e6e666a16f16fe87 src/cache/model.rs
875116e319c5bb9d281817fed626a94e468e9e3305f21dac99d3f2535e2fb157 src/cache/model/cache.rs
0ec4f6d0adea1ed80a3ca17c68c9c85502491da2904d9d690b4169ab9b025b21 src/cache/model/cold_metadata.rs
29bfce96b2ffd102d9c848c7b717a45fedb9aa44511f62f7a1f28a53dafd063a src/cache/model/entry.rs
@@ -348,80 +349,80 @@ bd831807ca4720b3a41a6ea1d9a6c0ed2d935225e2dacbd9313be064f2e4a20e src/cache/look
d890524999dc50aeb1dbda90264b46a1d53f08ac923d831b89ba82e4a564d923 src/cache/model/maintenance.rs
5441fa3a384add28b6fdd15300cf2648f8237dbfc211cad9117754203be76911 src/cache/model/settings.rs
55dcc0ad84d6b73689ba43e759d5685c406419c7733e23e78d5b072891ab17d7 src/cache/model/views.rs
e26dce02df9c043329ca164fed215d271781bce8fb03240f528331bf3df6b9d7 src/cache/policy.rs
0c8b0d550e94d4474dcdf991aa0340228dc5f2f904f39350e6e666a16f16fe87 src/cache/model.rs
989df11e246123f760ef31689370f3500e0913fcc6b70d7ac0c2c95e2f412fb0 src/cache/policy/request.rs
d6051d55168fe1db97980a54c22c708f4f325ba72bfc36f35a1851eb5dcc051d src/cache/policy/response.rs
e26dce02df9c043329ca164fed215d271781bce8fb03240f528331bf3df6b9d7 src/cache/policy.rs
7e2bcfbb2b57732cdc704d9df7a9298fb80e735e37984431bc936630e41e7f8a src/cache/range.rs
7ad96b3baae36ec49c90d03b380e749944fb3d54abb045c90db3a35f6b89b1c1 src/cache/tests.rs
9183fa8e6086e92fb148fc278fd866d44e3dd988c848a071e32066e44a1cbf0a src/cache/tests/cold_tier_tests.rs
3842cdcf7c3414b9c30f5fbed927b93e835eec3ef96d4c6ee44250038b834276 src/cache/tests/dashboard_tests.rs
d7a02eb7bad37e6a5524600aabb5e60ccf50e26c7a487e2806891c9d8acd48f3 src/cache/tests/http_invalidation_tests.rs
1659ffd8d227d2ec8b98e6a5e06b6143e6ac4ac06ab423c184980abcb47085da src/cache/tests/scope_tests.rs
d4429cf4f35fd8d0082108fb62602fde72c9c2fc6edfadfe3268c6a8214a8f89 src/cache/tests/tests.rs
af28534625859ff66011e10c54cff8f82c2cfa1c173c4d99b89c27c1437f0659 src/cache/tests/v022_cache_tests.rs
7ad96b3baae36ec49c90d03b380e749944fb3d54abb045c90db3a35f6b89b1c1 src/cache/tests.rs
22f8f16cf7383c579b19810819030072fc827681478e17068dddc85da8dc53ad src/cache/tickets.rs
e0146bf5d2e2e33aedcccd9205e5bd457559c9d77dc7ce266f8c165d1b867f0f src/compression.rs
4bdbde197d135fee8be20e93427eba8ca7b60b801717ed7ca734319bedb6ef05 src/compression/tests.rs
7bc8285cb2012506fd1a38a71dffa561f885f4ab65a9b2a7c8344319e3cd1cf6 src/config.rs
dbd57ef3307b937614cdfdb6c1df82acb80cbbef39f09d19fe2edfc24334aa58 src/config/backend.rs
e0146bf5d2e2e33aedcccd9205e5bd457559c9d77dc7ce266f8c165d1b867f0f src/compression.rs
56b3d37e7447995bec2fdda1c17dcc74b60958a63ca88b88ff57e56347d8b0b9 src/config/backend.rs
beb831a208c4e5f7ebe8327686b1e0edc66608c7032f895cb4fc3366b189aae0 src/config/compile.rs
dfa89a9e214d0b33b4efce8fa678f1fec17d17656e203acf84dfd09ce33868e0 src/config/internal_routing.rs
70741af46ad6f712b0c0e029c066d04c14a689eae0c831af264b09f6e5c41bae src/config/model.rs
40e09627e655d9319b576592f8d635e8c2da12905e5cee5952f7b6596010eb8c src/config/model.rs
fc993c356386a9ff17379d48a70ef388df5b2b10ca9ee04f06342f68f9dbbaae src/config/routing.rs
a7ccffdeeb258adae41c616022ed0181cf590d708d305d48c63317e4a3f68699 src/config/snapshot.rs
41a4fa4f9a919a8c848ee01d01423d82a136790c9d74f27319c1e5a6d9f18097 src/config/tests.rs
23541660151d9005555a07db6d491455d61b185f0cc70c3ec3a0bee2aef08dc3 src/db.rs
7bc8285cb2012506fd1a38a71dffa561f885f4ab65a9b2a7c8344319e3cd1cf6 src/config.rs
30f0b7269224a64e3f1ec9e6fbe9e27ddb1d308dc84cc9882e50545cf612695d src/db/certificate_api.rs
20ba52d3755c359d670968efa53b0604ff7cbed63a416f9f765b50a1f45074ac src/db/dashboard_presets.rs
2ff744223934685439374572b83fd639bb3eabdbac168bea4a6501a6e9ce0ee0 src/db/history.rs
c05a02618d476e4670b4f9930555f199cac776cdf09edb8fcc1a675695b8f99f src/db/tests.rs
23541660151d9005555a07db6d491455d61b185f0cc70c3ec3a0bee2aef08dc3 src/db.rs
19ab6c3ae37009c544291cb0b65c1bb9c721e168f95c1ed3fa88155aca7463ad src/demo.rs
e69031875ffec32c9f548c43bed5f1a5c4c626e9568c9173e3b093f9a8b73036 src/diagnostics.rs
b45d6040810c93ae2acb04ef6b0a59c189751edf9008cd81a4521c9e04e7f07d src/docker.rs
93a8a3f6ba12761ddcbd19f22f32fb2c0e6948b660fb774ddd7ebdba9b6562a5 src/docker/client.rs
4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs
5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs
a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs
2a02ea2e010cde1b1fe99b2e6baa80ea344ea6b332dbb3c4be795b71541f64a7 src/forwarding.rs
b45d6040810c93ae2acb04ef6b0a59c189751edf9008cd81a4521c9e04e7f07d src/docker.rs
057e652f216a1478638cbb53b1c64ac88c7f413a65825caacbf893c5af004a63 src/forwarding/tests.rs
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
2a02ea2e010cde1b1fe99b2e6baa80ea344ea6b332dbb3c4be795b71541f64a7 src/forwarding.rs
80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs
06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs
d3dc58628683472ef4d205fa5c42f0b7ef83cf90846146d96941cd3b36a40766 src/geoip/tests.rs
70091bdf6f38f03b6df8c8c4407c300677e57f293130122503f936500963d37c src/geoip/update.rs
c1b49bef28d87712e4c46138e70b52c4bcfb1a00196579031fac043d0b4aaa7b src/geoip/update_http.rs
9a912dad0b8d1e10410fd95d0da3684656092ef85cf8b23eda4ffe7fb92921d8 src/geoip/update_tests.rs
f6ebafafa82391f8cb3b5a09331f3762b6ca5a9fb64bafb5772cde3ab814dfcf src/headers.rs
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
e4f07bfb7eb75b6897ef812870caff158679a3046d64582428abe8c6df490dbe src/headers/tests.rs
9e21edc67a14ef8a2ce3dda0ed18784d0fd0be305973e5d216058b7ac802c6cb src/http3.rs
f6ebafafa82391f8cb3b5a09331f3762b6ca5a9fb64bafb5772cde3ab814dfcf src/headers.rs
eb57117938a447d9a6c2ec1806ebd9fecc640a68c88950a8b35fa9bad14d0dc6 src/http3/body.rs
ed25da264a2d25b40c4fcddc97c626a3a43d4f3a9be6c6d31af58b9e4925c198 src/http3/diagnostics.rs
37f54961d8f52b79a75fb2be31e81f4fdb1e66b06350345c4483d6197b8a0bbc src/http3/identity.rs
f2a8c2a98f4ccfb708f9d75a48ec7b351232be9b8a4364b7e63ba14d28eead7c src/http3/settings.rs
a31db7eac12b578b1eb5a74fae98bbe36312da95b7d7173135dbc41fc454da21 src/http3/tests.rs
9e21edc67a14ef8a2ce3dda0ed18784d0fd0be305973e5d216058b7ac802c6cb src/http3.rs
68fb5ab6a889995a66ad12cb6acf88c5d04f0aca572e2ae15f1a9cd171a5f24b src/lib.rs
2551189a3e5701371026c9abd2c5d44d37495f28fb67e220180421bfd315e3dd src/logging.rs
18c8d74c21a47f234be93551d7264b1f8f8c00d1bbfbe555d91618c58cea0740 src/logging/error_runtime.rs
33b4b7f69560c2acc0de0eac62604161d38dbd7044c5789a1352f0cd3882ea70 src/logging/format.rs
bc3873c6f22d1ee7fa76ae586547c9867e6eeaec8266e3ed1f529c87108df2b6 src/logging/format.rs
f5815d778cc1785723faed4abb15f9880f22b6524b5289fe1e7158905aac0986 src/logging/model.rs
42fef80af50735cd566c6801dad86ba81be98cf4a8ec7d45c2ea26f53f556a22 src/logging/runtime.rs
584782215ea78a88526cdb5515031d8d5b87910875e26c0024b93a06680f6791 src/logging/tests.rs
3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs
7ee1eb1d3b52b4c14303f8ca14a1924d4d6bbe75b2e43ce3ca47d7958f792c7d src/logging/tests.rs
2551189a3e5701371026c9abd2c5d44d37495f28fb67e220180421bfd315e3dd src/logging.rs
fc8e99eac83c083d65101f6402aacbcc8392d66508c31566a50de3ebcb12e7da src/main/admin_runtime.rs
42a9197475823f67c1ca762951ede5d7a2f10124bd6ed4d1a3365f4297be894c src/main/bootstrap.rs
5a98d0e249338e73aa57ae2e352dc16db2f79c2d042c1544ce1c2022da76a31c src/main/proxy_runtime.rs
4dd7c1084a6214c0f2513ebf86fe8ef144470695557772bfe25979edd64491ab src/main/shutdown.rs
0a6578a0f4023b9f8d73043bf942dc5e096ef9afb8c0d2f131c14f65d08ac01d src/main/shutdown/tests.rs
4dd7c1084a6214c0f2513ebf86fe8ef144470695557772bfe25979edd64491ab src/main/shutdown.rs
e5c2e9189777725f4afa1c3c052e4f75047ec3f2f486630d990f5d3c194bcc96 src/main/tasks.rs
49d7e104585c8791ac9a203dddd95802784b68c0ba65d6f90f3d57154b4c7871 src/metrics.rs
3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs
201ab702722d6d21585fe8bb49939150d61bfac7ed8dc198b98c2cc6098ffb5f src/metrics/global.rs
49b11df65b4fb0b5b727f9f867436584680b45e3f4fbd53f74bc0cab8b9a88ec src/metrics/vhost.rs
a54b5b34ea65f29723b89b81bf7968a238e8247b9c9760f3430d3504cb1017c9 src/metrics/vhost/tests.rs
a4702c95c7e5077c2ccff86e2e3216e1da6ff9ee4383ae199a0aff2f78db325f src/not_found.rs
49b11df65b4fb0b5b727f9f867436584680b45e3f4fbd53f74bc0cab8b9a88ec src/metrics/vhost.rs
49d7e104585c8791ac9a203dddd95802784b68c0ba65d6f90f3d57154b4c7871 src/metrics.rs
c915d30bbd7032d867e7916b4cd45f3bb7e7604d3f4558f4383c59394b4300c6 src/not_found/tests.rs
d66936cc15fd5d56566d40fa8cab93f80d94b05f10ac44478a30f63572a95e32 src/pages.rs
a4702c95c7e5077c2ccff86e2e3216e1da6ff9ee4383ae199a0aff2f78db325f src/not_found.rs
da40e8ece87245efc49a0c4e750526b1d19fd97565856bd5caf641fcc1dfe18d src/pages/tests.rs
0dba7b6a33866cc7bbe05509b01961e545892645d1dad4eb88ac8a4bec65e3bf src/prometheus.rs
d66936cc15fd5d56566d40fa8cab93f80d94b05f10ac44478a30f63572a95e32 src/pages.rs
cc6fb14351b009a16d07ed0f412c2bcc68cd7e551421aea03d8d97ceec9bee40 src/prometheus/core.rs
c46882ed26742d93806a976257674326e105b8ca1fc23f5ca81e317b268057aa src/prometheus/docker_misc.rs
d3c6b08088df0003144544936a55e2a3661aac85292821b594f00adfd19a2951 src/prometheus/model.rs
@@ -429,139 +430,140 @@ d3c6b08088df0003144544936a55e2a3661aac85292821b594f00adfd19a2951 src/prometheus
028e1dd9f2ec2afaf699a6b21c2d4b0d96e76f11f5e4ed009c233dadf0f3be51 src/prometheus/traffic.rs
e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus/vhosts_backends.rs
fb2d8d45cd2e947f783eb981b37fa418c39fcb327a3af17a648fadea7f37f888 src/prometheus/web_php.rs
4c7199c04e6d314e89825f80feaab35a2efb3b893511f70da0cda46ab1d0bc9e src/proxy/body.rs
0dba7b6a33866cc7bbe05509b01961e545892645d1dad4eb88ac8a4bec65e3bf src/prometheus.rs
f0f3e21350e4fb15fa2ae282da6b290857dcd6716e62b183e9ecb728b7c24c63 src/proxy/body/tests.rs
ea38488f8507609dbf6e5ae6e9c10f4045bc424a42e8eb11b608538a2d55d6ee src/proxy/body.rs
b88d1d7a6837a2a470e5863f1f3247878daee62791fc3d8e72baeb66469bdfbf src/proxy/cache_helpers.rs
aa49c9928bb91e3d5bae0d340d88396a0417b9edec6192d41e1403533319a897 src/proxy/cache_http_invalidation.rs
676c1b325fd8e55bcdd86451c7d1d8b8d347ca00e8da6b3e5b148a58c376a9b4 src/proxy/cache_plan.rs
3404b03096862de1050088f62cdd811ea1ab61d191f902501173107f81f23b9b src/proxy/cache_plan/tests.rs
676c1b325fd8e55bcdd86451c7d1d8b8d347ca00e8da6b3e5b148a58c376a9b4 src/proxy/cache_plan.rs
a8848cbdf85368548b003af2b43823c453866c472af4c7d2ff0ec7da14c44c8d src/proxy/error_fallback.rs
bf2e6bbd64fb45fceed54af0bff1c1e00da22b79c64dd8f5c23d2af0dbab0b7d src/proxy/forward/cache_lookup.rs
cfa941b60b5d167ec9d8e2566ea3342b588d1a9078a6ca711ad6062ddf00fec9 src/proxy/forward/response.rs
8168a42c3992d002e87fdd5d52a37f53dcdf69cbc859221350d888e94c305ab8 src/proxy/forward/route_cache.rs
7a5f43fe65e5a746eb84881da0bf784215081ead66d18faa7780533ad5df9c8c src/proxy/forward/route_cache.rs
96c97928986ca89c2d6d415d5b107b9cc7228410bae93a2c08c24974324f4d8d src/proxy/forward/success_finish.rs
0a921474ef2b93eabdaf3888651cf29130eace5ccb1e0f5d6bc25e6aa69280be src/proxy/forward/success_prepare.rs
bb24da9c9ab986a9c4d4144d4d1be0f661561d4afc755f298b58bc679b182850 src/proxy/forward/upstream.rs
11dccc239ac8a8e9ea06e884c2f2658f86b7807d14a59b8e052bc7dd75e86d63 src/proxy/forward/web_source.rs
facde401ba5bcbafdcea5524f236cfae37eec11144ceeea879e83314c84bc97e src/proxy/forward_entry.rs
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
c438066616527a3108f2ad53e3698ae9b2a80961714394cdd0a450c92f99d992 src/proxy/headers.rs
5cb7522ceb9a9f083a15596ac4898559295e38d1138c80099577211de8a868ec src/proxy/headers/tests.rs
c438066616527a3108f2ad53e3698ae9b2a80961714394cdd0a450c92f99d992 src/proxy/headers.rs
3f25429418941eb72c3847125340db74684ad316eb278ecf658953026fd41f4f src/proxy/mod.rs
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
c61738672f210e97333c8c89f9b35f8db454cbc1c36955288dfa4cfff2bbbc04 src/proxy/request_id.rs
6ff45990f6beb914e89924bec2c41022f3240a14357cf66ec4f8ada414a49767 src/proxy/request_id/tests.rs
8ad3619359f3befa14ea71ba43c638fea8f4d8a859cd07e4ed505731b8e32e72 src/proxy/tests/authority_tests.rs
d2df55b6ccfc7ceb8a3d2c49f664acd183448e3d932d5399d6692e87f79a094b src/proxy/request_id/tests.rs
96da894a86c05f8f467bf83cc23e56627ed3f9f490ffc936872d70126c8ecb41 src/proxy/request_id.rs
e56c65c85e42ee4fe6adf3655f3b2d176798e37c648db6bfb8e1602300b723eb src/proxy/tests/authority_tests.rs
1aa9bbbfb2659facdaeaf185e11221b1c8cd5fe6e23c7c1dacd3aa55e3db4fcb src/proxy/tests/cache_fast_path_tests.rs
0f2eacadf1447db8aa1c1a78005aa262e0f747096bfe7e0a0e41a2cd31a0d17f src/proxy/tests/h3_navigation_tests.rs
d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs
d5717fac0e7d2cb2939626eb95f4520241b677fe1f3e045df12c100315302a5a src/proxy/types.rs
8d7848645c25932589d0dfc54149bb71a51edd7188aed60dc4d6ca8179b361ab src/proxy/upstream_helpers.rs
2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs
7789a80202d606dcea3776345464a5487bf6124e4878bf8c94db91b67f0d4eb8 src/proxy/types.rs
1cbca5fbe91f40447e9eadc349e2d56f220119d14504f07ce90ed70f945a767e src/proxy/upstream_helpers.rs
d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs
d866e7e72dee6ff54c2418c34e9899cbcd7e4e027d9cbe2b625044dc9062e4ee src/proxy/websocket/handshake.rs
7e9c4d4c207badabf90630223964bb2ad641f06d4149843cab751f192b8b4adf src/proxy/websocket/tests.rs
a44421714aab37f7ceda19e3ec2512c645d2a3ce5dcb3da5bc7a0d87a442d6a8 src/proxy/websocket/tunnel.rs
15fabd38576b61f650c039acdbf7ac5a5bd24330062475b94315285b2dc258b6 src/proxy/websocket/validation.rs
545247eb00b0116efbd4cc913cc251195a55feded1530e0d8d8945abbae78039 src/rate_limit.rs
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
e7e3a898e2c769b1ef11e58a13c1053104ada93dd694f8a03ea4b647c44c7d69 src/rate_limit/tests.rs
d40ad63939097d2b91339127d5835a74b618d83b7ba31db0c512f05a82107d6e src/redirect.rs
545247eb00b0116efbd4cc913cc251195a55feded1530e0d8d8945abbae78039 src/rate_limit.rs
811c83ae9fd0f5a13d35b5443cd37843f82b9d600bee4bb136ba9b87732a5347 src/redirect/tests.rs
d0939e1b0829c25356a0b67ecb3a178c74936dd07baa15fee24eb32544db6cad src/runtime.rs
d40ad63939097d2b91339127d5835a74b618d83b7ba31db0c512f05a82107d6e src/redirect.rs
8d0d7b01871c8c588c40a54330a1053c85024655fbe38516ca6c94c3c19a8956 src/runtime/certificates.rs
2ccafa8a5a8462417f65d05a0c6c42f294afa4ad1067ea0b93623181546d1f96 src/runtime/constructors.rs
92b4b284f1b69dca58e4e3f76199f0aecc397eed26d10123b427af1bb8a4a57e src/runtime/docker.rs
ee2050b9f8efb45a209c91d76043a1ba29c4289978896aaf7e7d344d48fdc369 src/runtime/tests.rs
779137f320133ec96f73c8b3d357ad4f0b825ee2367e9e8300f4f143b38eecb7 src/runtime/types.rs
5947afaf7d070ea2047eb7969a8e0d84f981e91ace283f6506cf7685fb8afab9 src/runtime/updates.rs
208a378b14ca80e1412ffa19ef1b140594182971e2e797ee0bb95bcdb8404da7 src/secret_store.rs
d0939e1b0829c25356a0b67ecb3a178c74936dd07baa15fee24eb32544db6cad src/runtime.rs
6dca8da072a3bd610704a391bb6e25c9042971f4ebc60edb47665ce1d74cbc58 src/secret_store/tests.rs
6fa1d8c352bce7a16ff0208bc4cdbe80404715df0aff4ed26910a5df57cbbcd4 src/security.rs
208a378b14ca80e1412ffa19ef1b140594182971e2e797ee0bb95bcdb8404da7 src/secret_store.rs
0e5fc2aff856bf6fd959cba12b5ddd1e3b3cbeb0fbaadde906c811cdfafdaae7 src/security/tests.rs
98c52aa3b50d2e211ab752ccaf353c9a8c1500a87f9c1684b5dd0ba9d10e4619 src/server.rs
6fa1d8c352bce7a16ff0208bc4cdbe80404715df0aff4ed26910a5df57cbbcd4 src/security.rs
f2f6196632b2fdb2444189624f961edaf5bdfff57f700b0207b57e2491c1daa5 src/server/http.rs
050c48ea1127aa87dba92c074ab84e8346e65dbe253621ea53f8ca2f68d791ab src/server/http1_connection.rs
8c6ca5eaffb9acab57199d0349722703b903cfff3752baad2cf2596bd51188de src/server/listeners.rs
4424a99b0b688d1119ee25626778b4804aedfaa0799a4a9bc0ee99bc5c7ba96b src/server/sendfile.rs
71899a9b1897fa22ab9e8eb6a670054a6d382bad78714006b9b218d07ba99902 src/server/serve.rs
e9e478cf45b88994eaffd115f51764b6b07852ee3c941cafdabb20584d3dabc0 src/server/serve/tests.rs
71899a9b1897fa22ab9e8eb6a670054a6d382bad78714006b9b218d07ba99902 src/server/serve.rs
960a82e02f09d75141fe3c1eecca915b66f17fdc8167196cd2fe21d5e6859376 src/server/tls_runtime.rs
73ec453ac2850f6963f5bc33401cb8c92aa26d5123f15d68e6633860d91f265e src/settings.rs
98c52aa3b50d2e211ab752ccaf353c9a8c1500a87f9c1684b5dd0ba9d10e4619 src/server.rs
240ecdd70e8662ff3b84c21830dc0cbbbacadd755d56146e54214bc2887f068c src/settings/public_origin.rs
1f1121c3562c8f8f3fdee2113be2afffb8849be2d303e4c4409f87478b6e3307 src/settings/tests.rs
9dbf814870b8636770f579c3d36d16f5b6afe8bc66adfad356c26d7ae7dbce45 src/stream.rs
73ec453ac2850f6963f5bc33401cb8c92aa26d5123f15d68e6633860d91f265e src/settings.rs
254840126be2310426f6332f5efbe98147d9d8de1999731f40a3d13325c1dc6c src/stream/health.rs
bdc56be7dae85a97a4d5b8d46ef31ec4177292b1cadd507fe423b6d4ee1403bd src/stream/model.rs
de138a4ac68bcddba3a4f17ceba008bab643359d76a68726a755069f19334bed src/stream/pool.rs
6ec42b8647c95a0c2025462e0be29c71481ce1c3b1f4d4193926471f1cf06be0 src/stream/runtime.rs
9346cbd71e006e41a7188f28eb86f9b00bac822e4cb31c76c8d6d2ec5d4c4711 src/stream/tests.rs
3070d78a7b88c2e47ed59960fa35e7d220a4cd7d78e48f6c6d2bcc5ff77f9620 src/system_stats/linux.rs
9dbf814870b8636770f579c3d36d16f5b6afe8bc66adfad356c26d7ae7dbce45 src/stream.rs
d9123c623e2a97d09e41edaa29993b4b2c8cba39a3abdf2b48a205aa9a9c2939 src/system_stats/linux/tests.rs
3070d78a7b88c2e47ed59960fa35e7d220a4cd7d78e48f6c6d2bcc5ff77f9620 src/system_stats/linux.rs
751d59582fb92c84e0c0cc7c0855c9d0a545805c9ba3be61826e50fb96c297ed src/system_stats/mod.rs
050aa831b1012c4aaade63863f433f01bfacf5d3f95787e57975572116a30ab2 src/system_stats/tests.rs
7467d83066bb6986d8c9103597f7a7617ac677579ed46d573c573ff6ca57bbd9 src/tinyauth.rs
0e1d87554db55e724f30c41ec4a5c80bdb8fc9c8cba8700ac509864d4abf8871 src/tinyauth/tests.rs
1084cccd9de28acd6cb0f0813de7c0953910f51726f73f4acdb2e3ada4593976 src/tls.rs
7467d83066bb6986d8c9103597f7a7617ac677579ed46d573c573ff6ca57bbd9 src/tinyauth.rs
4972a436f8878dec5f9eb8ff17eb82670b0bfd9e94e821c516575479f5fc1919 src/tls/certificates.rs
436575144aa352498a4696f12c2258bef3a898f3f50e6fbc6d67c3b93afa099f src/tls/external_paths.rs
3e6947dde167f82a5efc407ccf9bd4c9f5b26cc8633d1418a10284bc1a1503d4 src/tls/server_config.rs
31c0f51a3a0bab433f438c3ba1b1cdaf3c63fae6086e7d99b3464a537f75a5e1 src/tls/tests.rs
2af2164ecdf2b7fd9eef7c6e358dec053dfac9aedaa501dbb75df742d18638b2 src/tls/tests/acme.rs
93b359e9defd62506729bc90e56c96783e2f14dd477dd67f337ca83149da7168 src/tls/tests/external_paths.rs
31c0f51a3a0bab433f438c3ba1b1cdaf3c63fae6086e7d99b3464a537f75a5e1 src/tls/tests.rs
10c4a187e3aeb0c72a8c8968ea0cc8bfae4e3cb2f3d834ebd3da1056c4171ec5 src/tls/types.rs
c2f802d95da4332fb8480e83f0c7502334457b3408141c1c4516c9ba320a0463 src/traffic.rs
1084cccd9de28acd6cb0f0813de7c0953910f51726f73f4acdb2e3ada4593976 src/tls.rs
36301bcbd20b557dd3d58e77a9175f3581947f577c0bb0b972899cec6786b03f src/traffic/tests.rs
c2f802d95da4332fb8480e83f0c7502334457b3408141c1c4516c9ba320a0463 src/traffic.rs
d88c92f5a2a7773bc3e715d24039db35da4df53881f1a0859eec819b96f06d3b src/transform/apply.rs
87f8af5261d79072f8215f1fb8a90715a58e7626544fd8176ce840e3111bdb92 src/transform/compile.rs
b2c5236589093454ec60984b785ad6a4af6149d47f6db37d8e08c17fec7a2c1b src/transform/css.rs
5478670cef87777b14a2dab308f5a9182df56d7ea2f5625bdc94aa7e3d661503 src/transform/html.rs
aa99b0d3029ef92c78371cb90e11f2026cce890d1be8e10ebd7fa9bc5cb13287 src/transform/html/tests.rs
a1d2fec07617c8a8dc425bfaeaf77c844152a0a174568a88a5baf4c317cc6c5c src/transform/js.rs
5478670cef87777b14a2dab308f5a9182df56d7ea2f5625bdc94aa7e3d661503 src/transform/html.rs
cf5195ab6bacccae0082da7b3f1bd0382b73fdeb3607f4ae569ca6daab9d10d0 src/transform/js/tests.rs
226ae561dc7b0243c04e55e28cdfd2b76acd48cf614d1919b81c196660b03cfb src/transform/json.rs
a1d2fec07617c8a8dc425bfaeaf77c844152a0a174568a88a5baf4c317cc6c5c src/transform/js.rs
216e24e29ea87be4a124f95f898e155f127fd959d7d5d9b30f8cd0b2b5018f30 src/transform/json/tests.rs
226ae561dc7b0243c04e55e28cdfd2b76acd48cf614d1919b81c196660b03cfb src/transform/json.rs
6a8c28be93fb590942493c075b29ace1527981542bd183f232aa82b62e951eca src/transform/mod.rs
fde2fa3aa94b30d8536afec1c11348cfbd922d0b5ff89e91d34266e8e3863e26 src/transform/model.rs
a4e1d2aa2913587c250f251356431097b76995aee6e8bf9e35c4978a95491de1 src/transform/svg.rs
0f53c64f442390ee20a59087a10f8f9ffb58053263e9aa848ddabfae1a6f3f51 src/transform/svg/tests.rs
a4e1d2aa2913587c250f251356431097b76995aee6e8bf9e35c4978a95491de1 src/transform/svg.rs
d1f3f53fedae0d8ef69eeee457f7d30ed5a670a1ed4ced60683400ee46680260 src/transform/tests.rs
6146c59f36cf419623747d497b3de685da6c84964df43317bebdcb21deb9ee09 src/update_check.rs
c42d000d1d576721af3ffe930589388693001b8113554a7658f6dcbc665b5d1b src/update_check/tests.rs
92efcdaafb9f5a8514daf63d7fcde29be977d7bdcd90162eda0ce00d6967ffbd src/upstream.rs
6146c59f36cf419623747d497b3de685da6c84964df43317bebdcb21deb9ee09 src/update_check.rs
ca695e246bee5389b3757fe8588e48bff5d0ad473bc8588ad4ba7b52c99d7dfa src/upstream/client.rs
ad29f3fc15c9b627b5d752af5adc9f99ee1364fec2df63f1e4509cb223988efb src/upstream/client_tests.rs
56c8a9d1c801be0f41b35c75cb42716c46976951948009c8707122c0a6a1eb58 src/upstream/client_tests.rs
75fef0a488d3f3dabfa03c2eabc5995442dfb3c9a1fde8eb6653a124dda25d43 src/upstream/protocol_tests.rs
8a112154b9631f62a54584302a3ff6a64e4f177ce79aba678b4cb23e466c6ea3 src/upstream/tests.rs
a6b4905c245a22eafb1aa535d303d0fa24e4353db94e1d67fd42b825bf8c5adb src/user_agent.rs
92efcdaafb9f5a8514daf63d7fcde29be977d7bdcd90162eda0ce00d6967ffbd src/upstream.rs
6d7cb73de7d8c6f3780736480f5f81ef08813f4626fd5ed494aca0552cf214a1 src/user_agent/tests.rs
3aa1b07cd26d4a5b9e5e94d1d6a712ef1397d827e7f1e8c01f7195b98d2c5f67 src/vhost.rs
a6b4905c245a22eafb1aa535d303d0fa24e4353db94e1d67fd42b825bf8c5adb src/user_agent.rs
6678192de56bc10c5cf82bdff8da14eece7685f409592450c07478333aa3b99c src/vhost/error_fallback.rs
63e9fc7f7be78dc3e16a89f015a56a2da6ba3aff0197b70ff48321dfb17529bb src/vhost/model.rs
35a0aca3eee8d47d1bab27c83d4532cd572fc3e17b5f0d4a8d4515a8f85b3d96 src/vhost/redirect.rs
9b16b5307e140db97363f56c9fac8319e56df846bb1c919d3bc06091ab6ce69c src/vhost/tests.rs
b7d0f7f5d0bbd74add3fda9b1308350c4f70f8b6debf979fb977a4f2f659c32f src/vhost/validation.rs
8d1eff4b24f01d137414ef5419f75dd281879228adb70409645b11b3c78c20e9 src/web_server/config.rs
3aa1b07cd26d4a5b9e5e94d1d6a712ef1397d827e7f1e8c01f7195b98d2c5f67 src/vhost.rs
9744e8c29ae72a4d9fbc9a76166a97f8292c4c312295ea40d1cb699e03d80a9d src/web_server/config/compile.rs
bcaa1ae15134d85668b05a88d8abea01e5d188f535ad83cd4459732fd56f2f44 src/web_server/config/model.rs
a863ad1337fbc353c0f42caea4ac8b3644792e2aa08c37f992b7882b4b8dc6d5 src/web_server/config/validation.rs
86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.rs
8d1eff4b24f01d137414ef5419f75dd281879228adb70409645b11b3c78c20e9 src/web_server/config.rs
200cba8cb3482e49db73d2c60cb60786175177c7d4be2a566cd281fec7ba7fca src/web_server/fastcgi/protocol.rs
cef44ccdf882d84892a64bdbfdc1ba01380fce5e5f86d2829b0f9613f738b964 src/web_server/fastcgi/response.rs
86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.rs
e15361622683268c5606a39a9e5db3938e630999767898391226e94ad2e421e8 src/web_server/filesystem.rs
d4fec3682532b0f2d11a0aefcd024f69fb623a39922ddb6c528a2d2de265347d src/web_server/htaccess.rs
ca3cd376b313af1ce0ee65081b3327eb213f000d5a215aa4ee6b764de9702126 src/web_server/htaccess/model.rs
76de1d11df75f4a64556b2821fd0a1774d26c9f0ca0e5f2b5fcfad0722d43d47 src/web_server/htaccess/parse.rs
66260d159986d41eab0dfde2cb9f0713fc1cc209c2fb47e9023bd24fd37919e8 src/web_server/htaccess/rewrite.rs
8ca74d9cc4470045301f70eb4a9fbabd0dbfe74434429148d2230df9f996c06c src/web_server/htaccess/tests.rs
d4fec3682532b0f2d11a0aefcd024f69fb623a39922ddb6c528a2d2de265347d src/web_server/htaccess.rs
3341264f6e8041ac5a5d3c4b1601a69787386116019b5febc1a5e22bdb1186f9 src/web_server/metrics.rs
0dbef9b71869fc52cf01e9cb400ffd48fb433ae625383edee6c6c2d735608a76 src/web_server/mod.rs
67ab8bef567452f123c4b9b46a942b39c93f1f765c128edca7ee808ff1c5e304 src/web_server/request.rs
d98c80663847d6d83073df074ebff65952755415574708abcc153bd8d3adfaa9 src/web_server/response.rs
833c9fea744b9649046e1a64d6fc6972fbf6cea640e1943b0db52b7ccfa9faad src/web_server/response/file.rs
ba793a140699956a9aef6bc79b0c731c481af8ab7ac3d816a28695743febcb30 src/web_server/response/file_body.rs
c2e54fd803a10f74490f183e8c030fd0e3e72f5de4e79c55bc393ab57d90ab9a src/web_server/response/file_body/tests.rs
ba793a140699956a9aef6bc79b0c731c481af8ab7ac3d816a28695743febcb30 src/web_server/response/file_body.rs
20509f9302b79d3a7be76dda6edaf5711826d3ddf47d19ebe07cb289427ca328 src/web_server/response/headers.rs
d98c80663847d6d83073df074ebff65952755415574708abcc153bd8d3adfaa9 src/web_server/response.rs
305c0109b4f869a99381a142853f9e2205f5c7c4f904b2dfa4643274bfc542af src/web_server/routing.rs
400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs
54cf72a82e023fe46513b9cf3130adfef1cd96c40f790c533016b4825d542e06 src/web_server/serve.rs
+81
View File
@@ -0,0 +1,81 @@
# RustProxy 0.34.0 HOT HIT follow-up (source candidate)
## What changed
The *existing* optimized HOT cache no longer removes/reinserts full entries at
HIT time. This follow-up further shortens the critical section without changing
cache policy or accepting an approximate eviction algorithm:
1. Store each published response's immutable headers in `Arc<HeaderMap>`.
Snapshot the Arc and the shared `Bytes` body while locked, release the HOT
mutex, then clone the headers and build the per-request response. Header
cloning is **not eliminated**, because per-request `Age`, `Cache-Status` and
transformation annotations must not mutate the stored representation.
2. Preserve exact per-HIT sequence, hit count and LRU ordering. Bypass the Vary
index traversal for the common direct-key/no-`Vary` representation; continue
selecting keyed variants for other representations.
3. Replace the per-domain flat pair-key map with a nested ordered map so a
known `(scope, domain)` is updated using borrowed keys. Preserve the 4096
distinct-domain limit and the dashboard wire format. Update cached-byte
accounting without allocating `String` keys.
4. When there are no active BAN rules, a cached response does not acquire the
BAN mutex or clone any BAN rules. Active rules still perform expiration,
matching and removal. Publication/removal of BAN rules updates an atomic
count; BAN and PURGE security checks remain enabled.
5. Remove repeated full HOT-entry `sweep()` scans. The existing
`retention_deadline()` explicitly returns `u64::MAX`: entries beyond their
freshness TTL are retained for conditional revalidation and `max-stale`.
Capacity/idle eviction and cold-tier GC remain responsible for memory/disk
retention. HTTP freshness checks and `Age` are unchanged.
6. Avoid allocating the static cache-reason text on each repeat occurrence:
previously registered reasons update by borrowed lookup. Recorded dashboard
fields, counters and reason labels are unchanged.
No new configuration, cache key, COLD on-disk format, migration, panel toggle or
feature flag was added. Cache query bypass, transformed representations, origin
validators, TTL, hot-to-cold demotion, backend selection and `sendfile` behavior
are intentionally unchanged.
## Tests and verification
Native Rust test file: `src/cache/tests/hot_hit_034_tests.rs`.
- Multi-threaded HOT access checks monotonic, unique hit counters and bounded
LRU/index growth.
- Transformed response HIT retains its ETag/extension; changes to the response
header map never mutate cached headers. Memory accounting and per-domain
saved-byte counters remain exact.
- Exact LRU eviction, selective BAN, stale revalidation retention and
conditional client `max-stale` are covered.
- The manual **ignored** in-process lookup microbenchmark measures the whole
`get_hot()` response construction, not network RPS.
```bash
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
CARGO_INCREMENTAL=0 cargo build --release --locked
python3 scripts/tests/cache_hot_034_static_test.py
cargo test --release --locked --lib manual_hot_lookup_microbenchmark -- --ignored --nocapture
```
For real HTTP RPS use the supplied process regression and same-host nginx
benchmark (`scripts/performance/README.md`) with **identical** configs. The
standard nginx comparator benchmark explicitly disables the cache; it cannot
measure this HOT-specific improvement. Compare two compiled RustProxy 0.34.0
binaries with the same cache-enabled, cache-warmed vhost and fixed no-query URL.
Check `X-Cache: HIT`, body SHA-256, exact `Cache-Status`, transform marker, Age,
ETag, p95/p99, CPU/request, memory and warm-up/eviction counts while measuring.
## Remaining performance bottlenecks
The HOT store still uses a shared mutex to provide exact LRU, per-key
generation checks and publication guarantees. Per-response `HeaderMap` cloning
is still required and now occurs outside this lock. Sharding the store or
making LRU eventually consistent would require a separate, extensively tested
cache architecture revision: doing that without compilation and live soak
would risk invalidation, accounting, BAN and tier-demotion regressions.
**Acceptance gate open:** the changed Rust source was not compiled in this
environment, new Rust tests were not run and no HOT HIT RPS improvement has been
measured. Source-only guards, SQL and upgrade tests do not establish a safe
production build.
+77
View File
@@ -0,0 +1,77 @@
# RustProxy 0.34.0: hot-path throughput revision
**Status: uncompiled source candidate.** This revision does not change the
application version, public API, dashboard controls, cache policy or tuning
defaults. It must not be labelled production-ready until the new Rust binary
passes compile, unit tests, live regressions and paired performance comparisons.
## Code changes and intended cost reductions
| Focus | Change | Kept behavior / risk to verify |
|---|---|---|
| Per-request identity | Draw a 256-bit nonce from the OS-seeded/reseeded thread-local CSPRNG instead of asking `getrandom` on every request. Preserve SHA-256 of nonce+monotonic sequence and the existing 64-hex ID format. | IDs remain non-predictable. Check ID uniqueness and raw upstream headers. |
| Upstream HTTP request target | Compile the validated scheme/authority `http::Uri` once with each immutable backend; parse only the request path+query and rebuild using typed `Uri::from_parts`. | Never accept a client-supplied authority, bypass DNS/address policies or change SNI. Test H1, H2, IPv6, query and percent-encoding. |
| Load balancing | Do not clone the member ID string for an existing smooth-WRR scoreboard entry. Continue the previously introduced no-mutex single-member non-cookie path. | Sticky-cookie modes, capacity limits, retry exclusions, health and drain remain unchanged. |
| Active health | Use already-compiled `Arc<HealthCheck>` when dispatching probes, avoiding a clone of its path/headers for each scheduled probe. | Same 250-ms supervisor, timeout, enabled/claim gate and health state transitions. |
| Per-frame streaming | Capture the leased backend once at first streamed request DATA (response already captured at wrapper construction). Do not take `Exchange.backend` mutex on every upload chunk. | Request/response byte counters, mid-stream failures, idle timeouts and lease lifetime remain active. |
| Pool recycling | Preserve exact request-count and elapsed-time recycling checks per request, with the existing independent 250-ms maintenance cycle. The pool factory/connection policy is unchanged. | Exact lifecycle semantics are more important than saving one cheap clock read; tune idle/age limits only after profiling. |
| Log history | Move completed `LogEntry` directly into bounded history when there are no live viewers or enabled external sinks, instead of deeply cloning its strings. Continue cloning and delivering to live viewers/sinks when enabled. | Same JSON fields, history size, drop counters, file/Loki/Logstash forwarding. |
| Route/HTTP instrumentation | Remove redundant route-ID mutex/write in forwarding and collapse H1/H2 version tallying into one branch. | Route is recorded once before forwarding; H1/H2 counters remain. |
These changes build on the previous 0.34.0 route-Arc, single-backend LB,
conditional retry/fallback-header cloning, atomic exchange flags and cache HOT
LRU changes. There are no new UI toggles. Pool defaults remain `max_idle_per_host`
4 and `pool_recycle_requests` 25,000; tune these only in separately labelled
trials. `sendfile: auto` remains opt-in and applies only to eligible Linux
plaintext H1 static WWW responses, not reverse proxy or TLS/H2/H3.
## Cache transform/query policy
An enabled `bypass_query` is a deliberate policy: `/theme.css?v=...` is a
`BYPASS` even when the origin sends `Cache-Control: public`. Turning the bypass
off permits a transformed CSS object to follow `MISS -> HIT` and appear in the
cache dashboard. An existing transformed HOT row with hit count and TTL is
positive evidence of this. This throughput revision does not modify the
cache eligibility logic or discard Age/ETag/Cache-Control headers.
## Verification already possible here
- **Old/reproducible reference executable only:** 110 passed, 0 failed, 1 skipped
(`compression.codec.zstd` skipped because the optional decoder was missing),
from live proxy/policy/cache/transform loopback regression. This does **not**
exercise the newly modified Rust source.
- **Old/reproducible reference executable only:** query bypass enabled gives
`BYPASS -> BYPASS`, disabled gives `MISS -> HIT` and a transformed representation;
the older Age regression is also reproducible on this old executable.
- Source-only invariants: `scripts/tests/rps_hotpath_034_static_test.py` plus
existing route/cache/sendfile checks. SQL migration and upgrade scripts pass.
- **Not performed:** Rust compilation or newly added Rust unit tests, live
behavior of the optimized binary, HTTP/2 live TLS, comparative RPS, CPU
profiling and long-duration memory/health soak. The environment does not
include `cargo`, `rustc` or `wrk`. No post-change RPS gain is claimed.
## Acceptance commands on a Rust-capable Linux host
```bash
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
CARGO_INCREMENTAL=0 cargo build --release --locked
python3 scripts/tests/rps_hotpath_034_static_test.py
python3 scripts/tests/cache_age_transform_034_live.py \
--binary "$(realpath target/release/rust-proxy)" \
--output /tmp/rustproxy034-cache-after
python3 scripts/performance/regression.py \
--binary optimized="$(realpath target/release/rust-proxy)" \
--out /tmp/rustproxy034-http-regression
```
Run `scripts/performance/benchmark.py` from the documented reproducible
before/after/nginx command in `scripts/performance/README.md`. Compare the
unchanged old 0.34.0 binary with the newly compiled source, not a baseline
with different feature flags. Record RPS at 1,040 B, 64 KiB, 1 MiB, p95/p99,
CPU/request, RSS, connection counts, errors and exact response-body hashes.
Use `perf record -g -p <proxy-pid>` during a stable request interval and inspect
`perf report --stdio` to identify the next true bottleneck.
Do not interpret Python origin self-tests, source inspections, or speculative
Amdahl models as measured RustProxy throughput.
+20
View File
@@ -22,6 +22,26 @@ for compilation and live HTTP, health, capacity, sticky, and H2 retry tests.
No RPS improvement, corrected nginx gap, or full functional parity has been
measured for this new revision. Details: `docs/PROXY_HOTPATH_0.34.0.md`.
## RPS-focused follow-up source revision (not yet compiled)
See `docs/PROXY_RPS_0.34.0.md` for detailed changes in request IDs, validated
upstream URI construction, LB scheduling, probes, streaming byte accounting,
unchanged pool generation lifetimes and log-history ownership. Source-only invariants pass.
A live test of the PREVIOUS executable (not this source revision) completed
110 passing cases, 0 failing cases and 1 skipped optional zstd decoder in the
proxy/policy/cache/transform harness. The old executable also confirms query-
string bypass is deliberate and reproduces the older Age/TTL bug. The new
binary has NOT been built or tested and its RPS gain is unknown.
## HOT HIT follow-up source revision (not yet compiled)
See `docs/CACHE_HOTPATH_0.34.0.md`. Response header cloning has moved outside
the global HOT lock, and redundant bounded stats key allocation, empty BAN
checks and full HOT TTL sweeps were removed. A new Rust concurrency/regression
suite and an ignored microbenchmark have been added. Existing COLD and Vary
behavior, exact LRU and cache semantics remain. This source has not been built
or run under Rust in this environment; no RPS gain is claimed.
## Changes and correctness boundaries
- Operational per-vhost saves (upstream keepalive/recycling, connection limits,
+11
View File
@@ -18,6 +18,17 @@ shutdown). Treat `--max-idle` tuning and recycles as separately labelled
experiments rather than mixing them into the primary comparison. There are no
new measured RPS figures in the source candidate.
## Follow-up RPS candidate on 0.34.0
`docs/PROXY_RPS_0.34.0.md` tracks the code changes and their regressions.
Compile before/after binaries from the two 0.34.0 source revisions and use
the paired benchmark below; a live run against the older binary cannot
establish a measured RPS improvement for the uncompiled candidate. Keep
`max_idle_per_host`, recycling, HTTP1/H2 settings and all application security
and logging features equal between runs. Treat any configuration tuning as a
separate experiment. Profile the resulting binary with `perf` only after
correctness checks pass.
## Offline cache-retention model for 0.34.0
`python3 scripts/performance/simulate_cache_034.py` compares cache invalidation
+20 -1
View File
@@ -65,7 +65,7 @@ def request(port, path, encoding='identity'):
'x_cache_hits': headers.get('x-cache-hits'), 'x_cache_resident_age': headers.get('x-cache-resident-age'),
'cache_status': headers.get('cache-status'), 'age': headers.get('age'),
'body': body.decode('utf-8', 'replace'), 'content_length': headers.get('content-length'),
'etag': headers.get('etag'), 'encoding': headers.get('content-encoding'), 'origin_count': COUNTS[path]}
'etag': headers.get('etag'), 'encoding': headers.get('content-encoding'), 'origin_count': COUNTS[path.split('?', 1)[0]]}
def dash(rust):
@@ -133,6 +133,25 @@ def run(binary: Path, output: Path, expect_old_age_bug: bool):
aged_br = find(snapshot, '/aged-br.css')
assert aged_br['transformed'] and aged_br['remaining_ttl_seconds'] >= 86_000, aged_br
# Query-string bypass is an explicit administrator rule. Even a
# transformed CSS with public max-age stays BYPASS while it is ON,
# then the *same* URL must become MISS -> HIT after disabling it.
# This is not a transform regression: it is a request policy.
cfg['cache']['bypass_query'] = True
rust.apply(cfg, 'query-bypass-enabled')
query_path = '/normal.css?v=transformed-public'
before = COUNTS['/normal.css']
q_bypass = [request(rust.reverse_port, query_path) for _ in range(2)]
assert [row['x_cache'] for row in q_bypass] == ['BYPASS', 'BYPASS'], q_bypass
assert COUNTS['/normal.css'] - before == 2, q_bypass
cfg['cache']['bypass_query'] = False
rust.apply(cfg, 'query-bypass-disabled')
q_cached = [request(rust.reverse_port, query_path) for _ in range(2)]
assert [row['x_cache'] for row in q_cached] == ['MISS', 'HIT'], q_cached
assert q_cached[1]['cache_status'] and 'transformed' in q_cached[1]['cache_status']
assert COUNTS['/normal.css'] - before == 3, q_cached
outcome['tests'].append({'query_bypass_policy': {'bypass': q_bypass, 'cached': q_cached}})
# Operational LB tuning must not invalidate a cached representation.
before = COUNTS['/normal.css']
cfg['vhosts'][0]['load_balancing']['keepalive'] = {'max_idle_per_host': 16}
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env python3
"""Source-only hot-cache safety guards; NOT a Rust compile, concurrency test or RPS result."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
read = lambda path: (ROOT / path).read_text()
hot = read("src/cache/lookup/hot.rs")
entry = read("src/cache/model/cache.rs")
model = read("src/cache/model/maintenance.rs")
inv = read("src/cache/invalidation/invalidate.rs")
dash = read("src/cache/dashboard.rs")
assert 'headers: Arc<HeaderMap>' in entry
assert 'Arc::clone(&entry.headers)' in hot
assert 'drop(store);' in hot
assert hot.index('drop(store);\n\n let mut response') < hot.index('headers.as_ref().clone()')
assert 'store.saved_body_hit(&ticket.scope, &ticket.domain, body_bytes)' in hot
assert 'store.lru.remove(&old_sequence)' in hot and 'store.lru.insert(sequence, storage_key)' in hot
assert '.sweep()' not in hot
assert 'fn sweep(' not in entry
assert 'fn saved_body_hit(' in entry
assert 'domains: BTreeMap<String, BTreeMap<String, DomainCounters>>' in entry
assert 'if self.ban_rule_count.load(Ordering::Acquire) == 0' in inv
assert 'bans.iter().any(|rule| rule.matches_hot' in inv
assert 'fn matching_hot_key(' in model and 'entry.vary.is_empty()' in model
assert 'store.domain_count = 0' in dash
assert 'c.reasons.get_mut(reason)' in dash
assert 'include!("tests/hot_hit_034_tests.rs")' in read('src/cache/tests.rs')
for path in (ROOT / 'src').rglob('*.rs'):
if '/tests/' not in path.as_posix() and path.name != 'tests.rs':
assert len(path.read_text().splitlines()) <= 500, path
print('PASS HOT path static invariants (no Rust compilation or throughput claim)')
+2 -1
View File
@@ -28,7 +28,8 @@ assert "scope_revision: draft.scope_revision" in read("src/cache/cold/worker.rs"
body = read("src/proxy/body.rs")
assert "response_backend: Option<Arc<crate::balance::Lease>>" in body
assert "lease.bytes(count, false);" in body
assert "exchange.backend_bytes(count, true)" in body
assert "request_backend: Option<Arc<crate::balance::Lease>>" in body
assert "lease.bytes(count, true);" in body
assert "cold_scope_fingerprint_preserves_unmodified_vhost" in read("src/cache/tests/cold_tier_tests.rs")
assert "stale_snapshot_cannot_issue_cache_ticket" in read("src/cache/tests/scope_tests.rs")
for file in (root / 'src').rglob('*.rs'):
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Source invariants for 0.34.0 RPS optimizations (NOT a Rust build or RPS test).
These guards reject accidental removal of important branch/safety conditions.
They must be paired with ``cargo test`` and live black-box HTTP acceptance.
"""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
def source(path):
return (ROOT / path).read_text(encoding="utf-8")
# Random request identifiers retain CSPRNG entropy, SHA-256, format and sequence.
identifier = source("src/proxy/request_id.rs")
assert "rand::random()" in identifier
assert "hash.update(sequence.to_be_bytes());" in identifier
assert "hash.update(nonce);" in identifier
assert "request_id_hex(&hash.finalize())" in identifier
assert "SysRng.try_fill_bytes" not in identifier
# Compiled/validated target authority survives request URI construction.
config = source("src/config/backend.rs")
model = source("src/config/model.rs")
request = source("src/proxy/types.rs")
assert "let request_origin_uri: http::Uri = request_origin.parse()?;" in config
assert "request_origin_uri," in config
assert "pub request_origin_uri: http::Uri," in model
assert "let path: http::uri::PathAndQuery = suffix.parse().ok()?;" in request
assert "let mut parts = backend.request_origin_uri.clone().into_parts();" in request
assert "parts.path_and_query = Some(path);" in request
assert "Uri::from_parts(parts).ok()" in request
# No new unbounded caches or disabled LB safety gates.
pool = source("src/balance/pool_core.rs")
assert "if self.members.len() == 1 && !self.cookie_affinity()" in pool
assert "try_acquire_active(member.definition.max_concurrent_requests)" in pool
assert "m.state.healthy()" in pool
assert "scheduling.get_mut(member.definition.id.as_str())" in pool
assert "scheduling.entry(member.definition.id.clone())" in pool
health = source("src/balance/lease_runtime.rs")
assert "async fn probe(member: Arc<Member>, settings: Arc<HealthCheck>)" in health
assert "checks.spawn(probe(Arc::clone(member), health.clone()))" in health
assert "tokio::time::timeout" in health
# Configurable keep-alive policy, exact recycle-request threshold and age backup.
upstream = source("src/upstream/client.rs")
assert "self.current.load_full()" in upstream
assert "request_due || age_due" in upstream
assert "let now = monotonic_millis();" in upstream
assert "let age_ms = now.saturating_sub(self.generation_started_ms.load(Ordering::Relaxed));" in upstream
assert "fn maintain_rotation_at(" in upstream
assert "self.factory.keepalive.pool_recycle_requests" in upstream
assert "self.factory.keepalive.pool_recycle_seconds" in upstream
assert "pool_max_idle_per_host(if keepalive.enabled" in upstream
assert "retry_canceled_requests(false)" in upstream
assert "observe_protocol" in upstream
# Request/response streamed payloads retain accounting and timeout/cancellation.
body = source("src/proxy/body.rs")
assert "request_backend: Option<Arc<crate::balance::Lease>>" in body
assert "this.request_backend = exchange.backend" in body
assert "lease.bytes(count, true)" in body
assert "lease.bytes(count, false)" in body
assert "exchange.request_bytes.fetch_add(count" in body
assert "exchange.response_bytes.fetch_add(count" in body
assert "get_or_insert_with(|| Box::pin(sleep_until(this.deadline)))" in body
assert "this.finish();" in body
# Persist history and preserve live streaming/sink drop accounting.
logs = source("src/logging/format.rs")
assert "let has_viewers = self.live.receiver_count() != 0;" in logs
assert "let has_sink = self.settings.load().enabled();" in logs
assert "external.take().expect(\"entry\")" in logs
assert "self.live.send(entry.clone())" in logs
assert "self.sink.try_send(entry)" in logs
assert "self.sink_dropped.fetch_add(1" in logs
# Never weaken routing, identity, HTTP version and cache eligibility while tuning.
route = source("src/proxy/forward/route_cache.rs")
assert "admin_session_passthrough" in route
assert "cache_plan.request_policy" in route
assert "exchange.set_cache_enabled" in route
assert "exchange.set_transform_enabled" in route
assert "upstream_uri(&backend, &origin_suffix)" in route
assert "headers::strip_hop_by_hop" in route
assert "pool.select_wait(context.client_ip" in route or ".select_wait(context.client_ip" in route
assert "pool.select_member_wait(id)" in route
assert "max_idle_per_host: 4" in source("src/balance/model.rs")
assert "pool_recycle_requests: 25_000" in source("src/balance/model.rs")
assert 'version = "0.34.0"' in source("Cargo.toml")
for file in (ROOT / "src").rglob("*.rs"):
if "/tests/" in file.as_posix() or file.name == "tests.rs":
continue
assert len(file.read_text(encoding="utf-8").splitlines()) <= 500, file
print("PASS RPS hot-path source invariants (STATIC ONLY; new binary not compiled)")
+6 -2
View File
@@ -296,7 +296,7 @@ impl Pool {
text
}
}
async fn probe(member: Arc<Member>, settings: HealthCheck) {
async fn probe(member: Arc<Member>, settings: Arc<HealthCheck>) {
let start = Instant::now();
let result = tokio::time::timeout(Duration::from_millis(settings.timeout_ms), async {
match settings.mode {
@@ -388,8 +388,12 @@ pub async fn health_loop(app: Weak<crate::runtime::App>, shutdown: CancellationT
let snapshot = app.config.load_full();
// Own an Arc for each candidate. JoinSet tasks require an
// owned, 'static member rather than a reference into snapshot.
// Health settings were already compiled into each member.
// Reuse the immutable Arc instead of cloning each HealthCheck
// and recalculating per-member overrides on every 250ms tick.
let members: Vec<_> = snapshot.pools.iter().flat_map(|pool|
pool.members.iter().map(move |member| (Arc::clone(member), pool.settings.health_for(&member.definition)))).collect();
pool.members.iter().map(|member|
(Arc::clone(member), Arc::clone(&member.health_settings)))).collect();
for (member, _) in &members {
member.state.sample_traffic();
// Time-based pool recycling must not depend on receiving a
+7 -3
View File
@@ -279,9 +279,13 @@ impl Pool {
let weight = member
.state
.weight(member.definition.weight, self.settings.slow_start_seconds);
let current = scheduling
.entry(member.definition.id.clone())
.or_insert(0.0);
let current = if let Some(current) =
scheduling.get_mut(member.definition.id.as_str())
{
current
} else {
scheduling.entry(member.definition.id.clone()).or_insert(0.0)
};
*current += weight;
total += weight;
if *current > best {
+14 -13
View File
@@ -6,9 +6,6 @@ impl Cache {
.unwrap_or_else(|e| e.into_inner())
.clone();
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
self.counters
.expired
.fetch_add(store.sweep() as u64, Ordering::Relaxed);
let n = |value: &AtomicU64| value.load(Ordering::Relaxed);
let hits = n(&self.counters.hits);
let misses = n(&self.counters.misses);
@@ -180,6 +177,7 @@ impl Cache {
store.revision = revision;
self.revision.store(revision, Ordering::Relaxed);
store.domains.clear();
store.domain_count = 0;
}
drop(store);
self.cancel_all_fill_flights();
@@ -247,9 +245,6 @@ impl Cache {
}
pub fn scope_view(&self, scope: &str) -> ScopeView {
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
self.counters
.expired
.fetch_add(store.sweep() as u64, Ordering::Relaxed);
let mut view = ScopeView {
entries: 0,
resident_bytes: 0,
@@ -276,26 +271,32 @@ impl Cache {
"MISS" => c.misses += 1,
_ => c.bypasses += 1,
}
*c.reasons.entry(reason.into()).or_default() += 1;
if let Some(count) = c.reasons.get_mut(reason) {
*count = count.saturating_add(1);
} else {
c.reasons.insert(reason.to_owned(), 1);
}
}
}
pub fn record_reason(&self, scope: &str, domain: &str, reason: &'static str) {
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
if let Some(c) = store.domain(scope, domain) {
*c.reasons.entry(reason.into()).or_default() += 1;
if let Some(count) = c.reasons.get_mut(reason) {
*count = count.saturating_add(1);
} else {
c.reasons.insert(reason.to_owned(), 1);
}
}
}
pub fn dashboard(&self, scope: Option<&str>, offset: usize, limit: usize) -> Dashboard {
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
self.counters
.expired
.fetch_add(store.sweep() as u64, Ordering::Relaxed);
let revision = store.revision;
let mut groups: BTreeMap<(String, String), DomainView> = store
.domains
.iter()
.filter(|((vhost, _), _)| scope.is_none_or(|s| s == vhost))
.map(|((vhost, domain), counters)| {
.filter(|(vhost, _)| scope.is_none_or(|s| s == vhost.as_str()))
.flat_map(|(vhost, domains)| domains.iter().map(move |(domain, counters)| (vhost, domain, counters)))
.map(|(vhost, domain, counters)| {
(
(vhost.clone(), domain.clone()),
DomainView {
+1 -1
View File
@@ -160,7 +160,7 @@ impl Candidate {
created_at,
body,
body_allocated: body_allocation,
headers: self.headers,
headers: Arc::new(self.headers),
received: self.plan.received,
last_access: Instant::now(),
resident_since: Instant::now(),
+27 -11
View File
@@ -1,17 +1,24 @@
impl Cache {
fn active_bans(&self) -> Vec<BanRule> {
if self.ban_rule_count.load(Ordering::Acquire) == 0 {
return Vec::new();
}
let now = SystemTime::now();
let mut bans = self.bans.lock().unwrap_or_else(|e| e.into_inner());
bans.retain(|rule| !rule.expired(now));
self.ban_rule_count.store(bans.len() as u64, Ordering::Release);
bans.clone()
}
fn hot_entry_banned(&self, key: &Key, entry: &Entry) -> bool {
if self.ban_rule_count.load(Ordering::Acquire) == 0 {
return false;
}
let now = SystemTime::now();
let banned = self
.active_bans()
.iter()
.any(|rule| rule.matches_hot(key, entry, now));
let mut bans = self.bans.lock().unwrap_or_else(|e| e.into_inner());
bans.retain(|rule| !rule.expired(now));
self.ban_rule_count.store(bans.len() as u64, Ordering::Release);
let banned = bans.iter().any(|rule| rule.matches_hot(key, entry, now));
if banned {
self.counters.ban_matches.fetch_add(1, Ordering::Relaxed);
}
@@ -19,11 +26,14 @@ impl Cache {
}
fn cold_entry_banned(&self, key: &Key, entry: &ColdEntry) -> bool {
if self.ban_rule_count.load(Ordering::Acquire) == 0 {
return false;
}
let now = SystemTime::now();
let banned = self
.active_bans()
.iter()
.any(|rule| rule.matches_cold(key, entry, now));
let mut bans = self.bans.lock().unwrap_or_else(|e| e.into_inner());
bans.retain(|rule| !rule.expired(now));
self.ban_rule_count.store(bans.len() as u64, Ordering::Release);
let banned = bans.iter().any(|rule| rule.matches_cold(key, entry, now));
if banned {
self.counters.ban_matches.fetch_add(1, Ordering::Relaxed);
}
@@ -31,10 +41,14 @@ impl Cache {
}
fn entry_banned_before_insert(&self, key: &Key, entry: &Entry) -> bool {
if self.ban_rule_count.load(Ordering::Acquire) == 0 {
return false;
}
let now = SystemTime::now();
self.active_bans()
.iter()
.any(|rule| rule.matches_hot(key, entry, now))
let mut bans = self.bans.lock().unwrap_or_else(|e| e.into_inner());
bans.retain(|rule| !rule.expired(now));
self.ban_rule_count.store(bans.len() as u64, Ordering::Release);
bans.iter().any(|rule| rule.matches_hot(key, entry, now))
}
pub fn bans(&self) -> Vec<BanView> {
@@ -46,6 +60,7 @@ impl Cache {
let before = bans.len();
bans.retain(|rule| rule.id != id);
let removed = bans.len() != before;
self.ban_rule_count.store(bans.len() as u64, Ordering::Release);
if removed {
self.counters.bans_removed.fetch_add(1, Ordering::Relaxed);
}
@@ -82,6 +97,7 @@ impl Cache {
created_at: now,
expires_at: now.checked_add(Duration::from_secs(ttl)).unwrap_or(now),
});
self.ban_rule_count.store(bans.len() as u64, Ordering::Release);
self.counters.bans_created.fetch_add(1, Ordering::Relaxed);
Ok(InvalidationResult {
operation: request.operation,
+2
View File
@@ -25,6 +25,7 @@ impl Cache {
fill_flight_sequence: AtomicU64::new(0),
collapse_waiters: AtomicU64::new(0),
bans: Mutex::new(Vec::new()),
ban_rule_count: AtomicU64::new(0),
ban_sequence: AtomicU64::new(0),
proactive_scheduled_keys: Mutex::new(HashSet::new()),
proactive_inflight: AtomicU64::new(0),
@@ -75,6 +76,7 @@ impl Cache {
fill_flight_sequence: AtomicU64::new(0),
collapse_waiters: AtomicU64::new(0),
bans: Mutex::new(Vec::new()),
ban_rule_count: AtomicU64::new(0),
ban_sequence: AtomicU64::new(0),
proactive_scheduled_keys: Mutex::new(HashSet::new()),
proactive_inflight: AtomicU64::new(0),
+49 -57
View File
@@ -1,29 +1,20 @@
impl Cache {
fn get_hot(&self, ticket: &Ticket, count_miss: bool) -> Option<Hit> {
// Only metadata, hit counters and exact LRU ordering require the store
// mutex. Materializing an HTTP response may allocate (headers, Body,
// Age formatting) and must be done *after* dropping that mutex.
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
let expired = store.sweep();
self.counters
.expired
.fetch_add(expired as u64, Ordering::Relaxed);
if store.revision != ticket.revision
|| store.generation != ticket.generation
|| store
.scope_generations
.get(&ticket.scope)
.copied()
.unwrap_or(0)
|| store.scope_generations.get(&ticket.scope).copied().unwrap_or(0)
!= ticket.scope_generation
|| store.key_generations.get(&ticket.key).copied().unwrap_or(0) != ticket.key_generation
|| store.key_generations.get(&ticket.key).copied().unwrap_or(0)
!= ticket.key_generation
{
return None;
}
if let Some(storage_key) = matching_hot_key(&store, ticket) {
// Don't remove and reinsert the entire Entry + variant index on
// every HOT hit. A cache hit changes only the LRU sequence, hits
// counter, and last-access clock; body/headers remain immutable.
let banned = store
.entries
.get(&storage_key)
let banned = store.entries.get(&storage_key)
.is_some_and(|entry| self.hot_entry_banned(&storage_key, entry));
if banned {
store.remove(&storage_key);
@@ -34,14 +25,9 @@ impl Cache {
release_allocator_memory();
return None;
}
let Some(entry) = store.entries.get(&storage_key) else {
return None;
};
let entry = store.entries.get(&storage_key)?;
let resident_age_seconds = hot_resident_age(entry).as_secs();
let age = entry
.initial_age
.saturating_add(entry.received.elapsed())
.as_secs();
let age = entry.initial_age.saturating_add(entry.received.elapsed()).as_secs();
let state = cache_state(
age,
entry.ttl,
@@ -53,48 +39,54 @@ impl Cache {
);
if state.servable() {
let sequence = store.next_sequence();
let (old_sequence, scope, domain, body_bytes, result) = {
let (old_sequence, headers, body, hits, transformed, skip_compression,
remaining_ttl, body_bytes) = {
let entry = store.entries.get_mut(&storage_key)?;
let old_sequence = entry.sequence;
entry.hits = entry.hits.saturating_add(1);
entry.last_access = Instant::now();
entry.sequence = sequence;
let mut response = Response::new(Body::from(entry.body.clone()));
*response.headers_mut() = entry.headers.clone();
if entry.transformed {
response
.extensions_mut()
.insert(crate::transform::CachedTransformed);
}
if entry.skip_compression {
response.extensions_mut().insert(crate::compression::Skip);
}
response.headers_mut().insert(
"age",
HeaderValue::from_str(&age.to_string()).expect("integer header"),
);
let result = Hit {
response,
body: entry.body.clone(),
hits: entry.hits,
transformed: entry.transformed,
remaining_ttl: entry.ttl.saturating_sub(age),
resident_age_seconds,
stale: state.stale(),
background_revalidate: state.background_revalidate(),
};
(old_sequence, entry.scope.clone(), entry.domain.clone(), entry.body.len(), result)
(
old_sequence,
Arc::clone(&entry.headers),
entry.body.clone(),
entry.hits,
entry.transformed,
entry.skip_compression,
entry.ttl.saturating_sub(age),
entry.body.len(),
)
};
store.lru.remove(&old_sequence);
store.lru.insert(sequence, storage_key);
// `store.bytes`, `store.logical_bytes` and the Vary schema index
// did not change. In particular, this path does not allocate a
// replacement variants Vec for every response.
if let Some(counter) = store.domain(&scope, &domain) {
counter.saved_body_bytes = counter.saved_body_bytes.saturating_add(body_bytes as u64);
}
// Preserve the exact LRU and domain-byte counters, without
// allocating a replacement variant index or String keys.
store.saved_body_hit(&ticket.scope, &ticket.domain, body_bytes);
self.counters.hits.fetch_add(1, Ordering::Relaxed);
return Some(result);
drop(store);
let mut response = Response::new(Body::from(body.clone()));
*response.headers_mut() = headers.as_ref().clone();
if transformed {
response.extensions_mut().insert(crate::transform::CachedTransformed);
}
if skip_compression {
response.extensions_mut().insert(crate::compression::Skip);
}
response.headers_mut().insert(
"age",
HeaderValue::from_str(&age.to_string()).expect("integer header"),
);
return Some(Hit {
response,
body,
hits,
transformed,
remaining_ttl,
resident_age_seconds,
stale: state.stale(),
background_revalidate: state.background_revalidate(),
});
}
}
if count_miss {
@@ -243,7 +235,7 @@ impl Cache {
target: entry.target.clone(),
tags: entry.tags.clone(),
created_at: entry.created_at,
headers: metadata.headers.clone(),
headers: Arc::new(metadata.headers.clone()),
body: body.clone(),
body_allocated,
received: Instant::now(),
-3
View File
@@ -51,9 +51,6 @@ impl Cache {
store.remove(&key);
}
store.remove(&storage_key);
self.counters
.expired
.fetch_add(store.sweep() as u64, Ordering::Relaxed);
let revision = store.revision;
let generation = store.generation;
let mut released_hot = false;
+1 -1
View File
@@ -77,7 +77,7 @@ impl Cache {
target: ticket.target.clone(),
tags,
created_at: ticket.created_at,
headers: headers.clone(),
headers: Arc::new(headers.clone()),
body: cached.body.clone(),
body_allocated: cached.body_allocated,
received: plan.received,
+20 -32
View File
@@ -111,7 +111,7 @@ struct Entry {
target: String,
tags: Vec<String>,
created_at: SystemTime,
headers: HeaderMap,
headers: Arc<HeaderMap>,
body: Bytes,
/// Backing allocation retained by `body`; can exceed `body.len()`.
body_allocated: usize,
@@ -181,6 +181,9 @@ fn hot_entry_cost(
.sum::<usize>();
body_allocated
.saturating_add(std::mem::size_of::<Entry>())
// Arc<HeaderMap> owns the actual map on a separate allocation.
.saturating_add(std::mem::size_of::<HeaderMap>())
.saturating_add(2 * std::mem::size_of::<usize>())
.saturating_add(index_heap)
.saturating_add(header_heap)
.saturating_add(string_vec_heap(vary))
@@ -333,8 +336,8 @@ struct Store {
control_revision: u64,
scope_generations: HashMap<String, u64>,
key_generations: HashMap<Key, u64>,
domains: BTreeMap<(String, String), DomainCounters>,
last_sweep: Option<Instant>,
domains: BTreeMap<String, BTreeMap<String, DomainCounters>>,
domain_count: usize,
}
impl Store {
fn index_variant(&mut self, base_key: Key, key: Key) {
@@ -360,39 +363,24 @@ impl Store {
Some(entry)
}
fn domain(&mut self, scope: &str, domain: &str) -> Option<&mut DomainCounters> {
let key = (scope.to_owned(), domain.to_owned());
// Cardinality is bounded independently of resident cache capacity.
if self.domains.len() >= 4096 && !self.domains.contains_key(&key) {
// The common path is a borrowed lookup: no allocation of scope/domain
// strings for every cache hit or log-reason counter update.
if self.domains.get(scope).is_some_and(|d| d.contains_key(domain)) {
return self.domains.get_mut(scope)?.get_mut(domain);
}
if self.domain_count >= 4096 {
return None;
}
Some(self.domains.entry(key).or_default())
self.domain_count += 1;
Some(self.domains.entry(scope.to_owned()).or_default()
.entry(domain.to_owned()).or_default())
}
fn sweep(&mut self) -> usize {
if self
.last_sweep
.is_some_and(|v| v.elapsed() < Duration::from_secs(1))
{
return 0;
fn saved_body_hit(&mut self, scope: &str, domain: &str, bytes: usize) {
// Every admitted HOT insertion registered its domain. If the bounded
// stats table is full, skip accounting for an unregistered domain.
if let Some(c) = self.domains.get_mut(scope).and_then(|d| d.get_mut(domain)) {
c.saved_body_bytes = c.saved_body_bytes.saturating_add(bytes as u64);
}
self.last_sweep = Some(Instant::now());
let stale: Vec<_> = self
.entries
.iter()
.filter(|(_, e)| {
let age = e.initial_age.saturating_add(e.received.elapsed()).as_secs();
age >= retention_deadline(
e.ttl,
e.stale_while_revalidate,
e.stale_if_error,
e.origin_etag.is_some() || e.origin_last_modified.is_some(),
)
})
.map(|(k, _)| *k)
.collect();
for key in &stale {
self.remove(key);
}
stale.len()
}
fn next_sequence(&mut self) -> u64 {
// No practical wrapping, but a wrap must not corrupt LRU bookkeeping.
+4
View File
@@ -73,6 +73,7 @@ pub struct Cache {
fill_flight_sequence: AtomicU64,
collapse_waiters: AtomicU64,
bans: Mutex<Vec<BanRule>>,
ban_rule_count: AtomicU64,
ban_sequence: AtomicU64,
proactive_scheduled_keys: Mutex<HashSet<Key>>,
proactive_inflight: AtomicU64,
@@ -347,6 +348,9 @@ fn hot_resident_age(entry: &Entry) -> Duration {
}
fn matching_hot_key(store: &Store, ticket: &Ticket) -> Option<Key> {
if store.entries.get(&ticket.key).is_some_and(|entry| entry.vary.is_empty()) {
return Some(ticket.key);
}
store
.variants
.get(&ticket.key)?
+3
View File
@@ -19,3 +19,6 @@ include!("tests/cold_tier_tests.rs");
#[cfg(test)]
include!("tests/http_invalidation_tests.rs");
#[cfg(test)]
include!("tests/hot_hit_034_tests.rs");
-1
View File
@@ -84,7 +84,6 @@ mod dashboard_tests {
for entry in store.entries.values_mut() {
entry.received = Instant::now() - Duration::from_secs(2 * 60 * 60);
}
store.last_sweep = None;
}
let dashboard = cache.dashboard(None, 0, 50);
assert_eq!(dashboard.matching_objects, 1);
+172
View File
@@ -0,0 +1,172 @@
mod hot_hit_034_tests {
use super::*;
use http_body_util::BodyExt;
fn settings() -> Settings {
Settings { enabled: true, ..Settings::default() }
}
async fn publish(
cache: &Arc<Cache>,
settings: &Settings,
path: &str,
body: &'static str,
transformed: bool,
) -> Ticket {
let ticket = cache.ticket_scoped(
34, "app", "http", "cache.example", "origin@127.0.0.1:8080",
path, &HeaderMap::new(),
).expect("cache ticket");
let mut response = Response::new(Body::from(body));
response.headers_mut().insert(
"cache-control", HeaderValue::from_static("public, max-age=31536000"),
);
response.headers_mut().insert(
"etag", HeaderValue::from_static("\"cached-representation\""),
);
if transformed {
response.extensions_mut().insert(crate::transform::Applied {
kind: crate::transform::Kind::Css,
original_bytes: body.len(),
transformed_bytes: body.len(),
});
}
let policy = plan(StatusCode::OK, response.headers(), settings, Duration::ZERO)
.expect("public static object cacheable");
cache.wrap(response, ticket.clone(), policy, settings)
.into_body().collect().await.unwrap();
ticket
}
#[tokio::test]
async fn hot_hit_headers_are_independent_and_lru_counters_remain_exact() {
let cache = Cache::new(34);
let settings = settings();
let ticket = publish(&cache, &settings, "/theme.css", "body{color:red}", true).await;
let initial = cache.view();
let before = cache.dashboard(Some("app"), 0, 10);
assert_eq!(before.matching_objects, 1);
assert!(before.objects[0].transformed);
for i in 1..=500 {
let hit = cache.get(&ticket).expect("HOT cache hit");
assert_eq!(hit.hits, i);
assert!(hit.transformed);
assert!(hit.response.extensions().get::<crate::transform::CachedTransformed>().is_some());
assert_eq!(hit.response.headers()["etag"], "\"cached-representation\"");
let mut independent = hit.response;
independent.headers_mut().insert("etag", HeaderValue::from_static("\"client-only\""));
if i == 500 {
assert_eq!(independent.into_body().collect().await.unwrap().to_bytes(), Bytes::from_static(b"body{color:red}"));
}
}
let after = cache.view();
assert_eq!(after.entries, 1);
assert_eq!(after.hot_allocated_bytes, initial.hot_allocated_bytes);
assert_eq!(after.hot_logical_bytes, initial.hot_logical_bytes);
assert_eq!(after.hits.saturating_sub(initial.hits), 500);
let store = cache.store.lock().unwrap();
assert_eq!(store.entries.len(), 1);
assert_eq!(store.lru.len(), 1);
assert_eq!(store.variants.len(), 1);
drop(store);
let dashboard = cache.dashboard(Some("app"), 0, 10);
assert_eq!(dashboard.objects[0].hits, 500);
assert_eq!(dashboard.domains[0].counters.saved_body_bytes, 500 * 15);
assert_eq!(cache.get(&ticket).unwrap().response.headers()["etag"], "\"cached-representation\"");
}
#[tokio::test]
async fn concurrent_hot_hits_have_unique_monotonic_counts_and_bounded_indexes() {
let cache = Cache::new(34);
let ticket = publish(&cache, &settings(), "/shared.css", "cachebody", false).await;
let workers = 6;
let per_worker = 300;
let mut threads = Vec::new();
for _ in 0..workers {
let cache = Arc::clone(&cache);
let ticket = ticket.clone();
threads.push(std::thread::spawn(move || {
let mut hits = Vec::with_capacity(per_worker);
for _ in 0..per_worker {
let hit = cache.get(&ticket).expect("concurrent HOT hit");
assert_eq!(hit.response.headers()["etag"], "\"cached-representation\"");
hits.push(hit.hits);
}
hits
}));
}
let mut counts: Vec<_> = threads.into_iter().flat_map(|t| t.join().unwrap()).collect();
counts.sort_unstable();
assert_eq!(counts, (1..=(workers * per_worker) as u64).collect::<Vec<_>>());
assert_eq!(cache.view().hits, (workers * per_worker) as u64);
let store = cache.store.lock().unwrap();
assert_eq!(store.lru.len(), 1);
assert_eq!(store.entries.len(), 1);
}
#[tokio::test]
async fn hot_lru_eviction_and_ban_are_preserved() {
let cache = Cache::new(34);
let settings = Settings { max_entries: 2, ..settings() };
let a = publish(&cache, &settings, "/a.css", "AAAA", false).await;
let b = publish(&cache, &settings, "/b.css", "BBBB", false).await;
assert!(cache.get(&a).is_some(), "A becomes most recently used");
let c = publish(&cache, &settings, "/c.css", "CCCC", false).await;
assert!(cache.get(&a).is_some());
assert!(cache.get(&b).is_none(), "B is least recently used");
assert!(cache.get(&c).is_some());
let ban = cache.invalidate(InvalidationRequest {
operation: InvalidationOperation::Ban,
selector: InvalidationSelector::Exact,
value: "/a.css".into(),
host: Some("cache.example".into()),
vhost: Some("app".into()),
ttl_seconds: Some(3600),
confirm_wide: false,
}).unwrap();
assert_eq!(cache.ban_rule_count.load(Ordering::Acquire), 1);
assert!(cache.get(&a).is_none(), "BAN must block cached A");
assert!(cache.remove_ban(ban.ban_id.unwrap()));
assert_eq!(cache.ban_rule_count.load(Ordering::Acquire), 0);
assert!(cache.get(&c).is_some(), "unrelated C must remain cached");
}
#[tokio::test]
#[ignore = "manual in-process timing only; NOT HTTP RPS"]
async fn manual_hot_lookup_microbenchmark() {
let cache = Cache::new(34);
let ticket = publish(&cache, &settings(), "/bench.css", "smallbody", true).await;
for _ in 0..1_000 {
std::hint::black_box(cache.get(&ticket).unwrap());
}
let iterations = 100_000;
let start = Instant::now();
for _ in 0..iterations {
std::hint::black_box(cache.get(&ticket).unwrap());
}
let duration = start.elapsed();
eprintln!("HOT lookup incl. response materialization: {:.1} ns/op ({} hits); NOT network RPS",
duration.as_secs_f64() * 1e9 / iterations as f64, iterations);
assert_eq!(cache.view().hits, iterations + 1_000);
}
#[tokio::test]
async fn stale_object_remains_available_for_explicit_max_stale() {
let cache = Cache::new(34);
let ticket = publish(&cache, &settings(), "/old.css", "oldbody", false).await;
{
let mut store = cache.store.lock().unwrap();
store.entries.values_mut().next().unwrap().received =
Instant::now() - Duration::from_secs(700_000);
}
assert!(cache.get(&ticket).is_none(), "expired freshness is not an unconditional HIT");
assert_eq!(cache.view().entries, 1, "stale representation must survive for revalidation");
let mut stale_ticket = ticket;
stale_ticket.request_headers.insert(
"cache-control", HeaderValue::from_static("max-stale"),
);
assert!(cache.get(&stale_ticket).unwrap().stale);
}
}
+1 -5
View File
@@ -109,10 +109,6 @@ impl Cache {
}
pub fn revalidation(&self, ticket: &Ticket) -> Option<Revalidation> {
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
let expired = store.sweep();
self.counters
.expired
.fetch_add(expired as u64, Ordering::Relaxed);
if store.revision != ticket.revision
|| store.generation != ticket.generation
|| store
@@ -142,7 +138,7 @@ impl Cache {
.saturating_add(entry.received.elapsed())
.as_secs();
Some(Revalidation {
headers: entry.headers.clone(),
headers: entry.headers.as_ref().clone(),
body: entry.body.clone(),
body_allocated: entry.body_allocated,
_transient_leases: Vec::new(),
+4 -1
View File
@@ -94,11 +94,14 @@ pub(crate) fn compile_backend_with_timeout(
connect_timeout,
)?
};
let request_origin = format!("{}://{request_authority}", url.scheme());
let request_origin_uri: http::Uri = request_origin.parse()?;
Ok(CompiledBackend {
address_policy,
id: backend.id.clone(),
origin: format!("{}://{authority}", url.scheme()),
request_origin: format!("{}://{request_authority}", url.scheme()),
request_origin,
request_origin_uri,
host_header: authority.parse()?,
preserve_host: backend.preserve_host,
protocol: backend.protocol,
+2
View File
@@ -174,6 +174,8 @@ pub struct CompiledBackend {
pub origin: String,
/// HTTP request origin. For HTTPS this may carry a separate verified TLS name.
pub request_origin: String,
/// Parsed once at configuration publish; reused for every upstream request.
pub request_origin_uri: http::Uri,
pub host_header: HeaderValue,
pub preserve_host: bool,
pub protocol: UpstreamProtocol,
+21 -7
View File
@@ -136,8 +136,17 @@ impl Logs {
self.settings.load().capture_country
}
pub fn push(&self, entry: LogEntry) {
// String cloning and eviction cleanup do not need the history mutex.
let history_entry = entry.clone();
let has_viewers = self.live.receiver_count() != 0;
let has_sink = self.settings.load().enabled();
// The usual history-only configuration needs no deep copy at all.
// When external consumers are present, clone exactly for history and
// keep the original entry available for streaming/sink delivery.
let mut external = Some(entry);
let history_entry = if has_viewers || has_sink {
external.as_ref().expect("entry").clone()
} else {
external.take().expect("entry")
};
let evicted = if let Ok(mut entries) = self.entries.try_lock() {
let evicted = if entries.len() == HISTORY_CAPACITY {
entries.pop_front()
@@ -150,13 +159,18 @@ impl Logs {
self.dropped.fetch_add(1, Ordering::Relaxed);
None
};
// Destroy evicted strings outside of the history lock.
drop(evicted);
// No live viewers means no reason to allocate/clone a second LogEntry.
// History retention and external sinks remain unchanged.
if self.live.receiver_count() != 0 {
let _ = self.live.send(entry.clone());
if let Some(entry) = external {
if has_viewers {
let _ = self.live.send(entry.clone());
}
// Snapshot the sink choice once. This preserves sink drop
// accounting and avoids a second ArcSwap load per request.
if has_sink && self.sink.try_send(entry).is_err() {
self.sink_dropped.fetch_add(1, Ordering::Relaxed);
}
}
self.push_sink(entry);
}
fn push_sink(&self, entry: LogEntry) {
if self.settings.load().enabled() && self.sink.try_send(entry).is_err() {
+36
View File
@@ -422,4 +422,40 @@ mod tests {
assert_eq!(stats.sent, 1);
assert_eq!(stats.errors, 0);
}
#[test]
fn history_only_mode_preserves_exact_fields_without_any_live_or_sink_copies() {
let logs = Logs::default();
let item = entry();
let expected = serde_json::to_value(&item).unwrap();
logs.push(item);
let saved = logs.recent();
assert_eq!(saved.len(), 1);
assert_eq!(serde_json::to_value(&saved[0]).unwrap(), expected);
assert_eq!(logs.stats().queue_dropped, 0);
}
#[test]
fn live_viewers_receive_same_event_as_persisted_history() {
let logs = Logs::default();
let mut live = logs.subscribe();
let item = entry();
let expected = serde_json::to_value(&item).unwrap();
logs.push(item);
assert_eq!(serde_json::to_value(live.try_recv().unwrap()).unwrap(), expected);
assert_eq!(serde_json::to_value(&logs.recent()[0]).unwrap(), expected);
}
#[test]
fn enabled_external_sink_retains_existing_queue_drop_accounting() {
// The default test Logs has a closed sink receiver. Delivering to a
// configured external sink must still account for its failure.
let logs = Logs::default();
let mut settings = Settings::default();
settings.file.enabled = true;
logs.configure(settings);
logs.push(entry());
assert_eq!(logs.recent().len(), 1);
assert_eq!(logs.stats().queue_dropped, 1);
}
}
+14 -1
View File
@@ -301,6 +301,7 @@ pub struct MeteredBody {
/// For a response, backend selection is final before this wrapper is made.
/// Keep the selected lease alive and avoid locking Exchange.backend per frame.
response_backend: Option<Arc<crate::balance::Lease>>,
request_backend: Option<Arc<crate::balance::Lease>>,
direction: Direction,
timer: Option<Pin<Box<Sleep>>>,
deadline: tokio::time::Instant,
@@ -317,6 +318,7 @@ impl MeteredBody {
inner,
exchange: Some(exchange),
response_backend,
request_backend: None,
direction,
// Ready/empty bodies never need a timer allocation. Keep the
// original deadline so a delayed first poll still times out.
@@ -330,6 +332,7 @@ impl MeteredBody {
self.finished = true;
self.timer.take();
self.response_backend.take();
self.request_backend.take();
self.exchange.take();
}
}
@@ -350,7 +353,17 @@ impl HttpBody for MeteredBody {
if let (Some(data), Some(exchange)) = (frame.data_ref(), this.exchange.as_ref()) {
let count = data.len() as u64;
match this.direction {
Direction::Request => exchange.backend_bytes(count, true),
Direction::Request => {
// Backend is selected before a non-empty upstream
// upload is polled. No retry of a streamed write.
if this.request_backend.is_none() {
this.request_backend = exchange.backend
.lock().unwrap_or_else(|e| e.into_inner()).clone();
}
if let Some(lease) = &this.request_backend {
lease.bytes(count, true);
}
}
Direction::Response => {
if let Some(lease) = &this.response_backend {
lease.bytes(count, false);
+3 -1
View File
@@ -125,7 +125,9 @@ let cache_settings = route
let transform_settings = route.frontend.as_ref().map(|f| &f.transform);
let scope = route.frontend.as_ref().map(|f| f.id.as_str()).unwrap_or("");
if let Some(exchange) = request.extensions().get::<Arc<Exchange>>() {
*exchange.route.lock().unwrap_or_else(|e| e.into_inner()) = Some(route.id.clone());
// The resolved route was already recorded before local response, LB and
// ACL decisions. Do not acquire the route mutex and copy its ID twice on
// every reverse-proxy request.
exchange.set_cache_enabled(cache_settings.active_for(route.cache));
exchange.set_transform_enabled(transform_settings.is_some_and(|settings| settings.enabled));
}
+4 -5
View File
@@ -1,11 +1,10 @@
fn request_id_hash(sequence: u64) -> String {
use rand::TryRng;
use sha2::{Digest, Sha256};
let mut nonce = [0u8; 32];
let mut rng = rand::rngs::SysRng;
rng.try_fill_bytes(&mut nonce)
.expect("secure operating system random source unavailable");
// Thread-local CSPRNG is seeded (and periodically reseeded) from the OS.
// Do not make a getrandom syscall on the hot path for every HTTP request.
// Keep hashing the per-process sequence as before for the same ID format.
let nonce: [u8; 32] = rand::random();
let mut hash = Sha256::new();
hash.update(sequence.to_be_bytes());
hash.update(nonce);
+1 -1
View File
@@ -25,7 +25,7 @@ mod request_id_tests {
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)));
assert!(
seen.insert(id),
"fixed sequence must still use independent OS entropy"
"fixed sequence must still use independent CSPRNG output"
);
}
}
+19
View File
@@ -595,4 +595,23 @@ mod graceful_h2_tests {
assert!(!is_graceful_h2_goaway(&protocol));
assert!(!is_graceful_h2_goaway(&local));
}
#[test]
fn compiled_request_origin_matches_previous_uri_parser() {
let mut config = crate::config::demo_config("127.0.0.1:9000".parse().unwrap());
config.vhosts[0].backends[0].url = "https://127.0.0.1:9000".into();
config.vhosts[0].backends[0].tls_server_name = Some("backend.example".into());
let snapshot = crate::config::compile(config, 1, &validation_policy()).unwrap();
let backend = &snapshot.pools[0].members[0].backend;
assert_eq!(backend.request_origin_uri.to_string(), backend.request_origin);
for suffix in [
"/", "/static/main.css", "/path?x=1&y=2", "/p%2Fq?url=https%3A//example.org/a",
"/?q=%C5%82&empty=", "/double//slash", "/item#frag",
] {
let previous = format!("{}{suffix}", backend.request_origin).parse::<Uri>().ok();
assert_eq!(upstream_uri(backend, suffix), previous, "{suffix}");
}
assert!(upstream_uri(backend, "http://attacker.invalid/").is_none());
assert_eq!(backend.origin, "https://127.0.0.1:9000");
}
}
+7 -1
View File
@@ -152,7 +152,13 @@ fn retry_safe_method(method: &Method) -> bool {
}
fn upstream_uri(backend: &crate::config::CompiledBackend, suffix: &str) -> Option<Uri> {
format!("{}{suffix}", backend.request_origin).parse().ok()
// Reuse the validated scheme and authority from the immutable compiled
// backend. Only the untrusted path/query is parsed per request. This also
// prevents accidental authority changes from rewritten paths.
let path: http::uri::PathAndQuery = suffix.parse().ok()?;
let mut parts = backend.request_origin_uri.clone().into_parts();
parts.path_and_query = Some(path);
Uri::from_parts(parts).ok()
}
fn legacy_websocket_origin(
+5 -8
View File
@@ -56,14 +56,11 @@ async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next
|| (request.version() == Version::HTTP_3
&& request.method() == Method::CONNECT
&& websocket::h3_protocol(&request) == Some("websocket"));
if request.version() == Version::HTTP_11 {
state.metrics.http1_requests.fetch_add(1, Ordering::Relaxed);
}
if request.version() == Version::HTTP_2 {
state.metrics.http2_requests.fetch_add(1, Ordering::Relaxed);
}
if request.version() == Version::HTTP_3 {
state.metrics.http3_requests.fetch_add(1, Ordering::Relaxed);
match request.version() {
Version::HTTP_11 => { state.metrics.http1_requests.fetch_add(1, Ordering::Relaxed); }
Version::HTTP_2 => { state.metrics.http2_requests.fetch_add(1, Ordering::Relaxed); }
Version::HTTP_3 => { state.metrics.http3_requests.fetch_add(1, Ordering::Relaxed); }
_ => {}
}
let id = state.metrics.requests.fetch_add(1, Ordering::Relaxed) + 1;
let request_id = request_id_hash(id);
+16
View File
@@ -96,5 +96,21 @@ mod tests {
}
assert_eq!(client.rotation_stats().rotations, 0);
}
#[test]
fn request_recycling_still_has_exact_threshold_after_clock_sampling() {
let backend = backend("requests-boundary", 18085);
let mut keepalive = crate::balance::Keepalive::default();
keepalive.pool_recycle_requests = 1_000;
keepalive.pool_recycle_seconds = 0;
let client = client_with_keepalive(
&backend, AddressPolicy::new(&backend).unwrap(), false, &keepalive,
).unwrap();
for _ in 0..1_000 { client.inner.client_for_request(); }
assert_eq!(client.rotation_stats().rotations, 0);
client.inner.client_for_request();
assert_eq!(client.rotation_stats().rotations, 1);
assert_eq!(client.rotation_stats().generation_requests, 1);
}
}