v0.34.0_p1

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-17 22:33:00 +02:00
1 parent 00f29f92c8
commit 0a8bc6f040
21 files changed
+672 -144

No files matched your search

+6
View File
@@ -5,6 +5,12 @@
## 0.34.0
- 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.
- Add 100,000-case deterministic freshness simulation, executable loopback HTTP regression (including Brotli), and Rust HOT/COLD age/transform/persistence tests. Production compilation and post-change RPS measurement are still required.
- Optimize reverse-proxy hot path with shared immutable compiled route handles,
reusing the observer's host/location selection in forwarding without changing
ACL, rate, canonical authority or protocol enforcement.
+83 -80
View File
@@ -2,7 +2,7 @@
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
46bab1d2c2d6e0a80d57a6b232b1ba4c220884b95cd45ddccbafce2aa160cddb AGENTS.md
37ed26e41820a69fe6417e7600c9ec5a2eeab69fd11e5a389be912a2a3cd7642 CHANGELOG.md
4e0a7371f060063a6a73ae1c1a2faba2de8d227118be2406c88ca432c90305eb CHANGELOG.md
ca201e3cd3f9842eae0006d0b40a0a72bcd3d4da6e4e5ecaf200d1b9a1390db2 Cargo.lock
fec5f1bae9a59ef1fabd27c5e45d31448dd8f811736eebfa8826b3806a7e3349 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
@@ -59,6 +59,7 @@ deea89aed410c0619bdbdb12437edc7aff9dca2fec7fdc059e918c649fc36b51 docs/PROXY_HOT
887d1fdcc3b546b024afcd18b1716b8e3a74a3800012bf4bda7a122d2eabe233 docs/README.md
6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md
0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md
540fa46716d562bfc846d2f7bac0515380831ddbdcaa01ff4ac86be4ff3d3aa0 docs/RELEASE_0.34.0_CACHE_FIX_PL.md
d85c5567fc6a7714b96ad7cfe4ee9ff2085f84c4f31534a6484340d03a73e0f7 docs/RENEWAL.md
cd4ed3af26eb51c9ff6ef3dda9d52ff5120bcfc78c842bce34a639cf85b6aea9 docs/ROADMAP_HISTORY.md
759bf2987f2b602488829d1856fa33d57c2a3b23bc681ef5a931b9633af3a533 docs/SECRET_STORE_BACKUP.md
@@ -100,8 +101,8 @@ cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/r
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md
462339bc35535fa69ebd95aaf43e9405913d940406bfa14b27d15afb62accef5 langs/en_US.json
22efd93e26f71bd78c1e6cf83a9f0aaab3a8a5a4adb98822cbf73ba6e22cb991 langs/pl_pl.json
315f673be9a929069f6ad0c60a215d30d7c3dc2de4105e208810c371c488b268 langs/en_US.json
ca68b7475d3b744b3cd091140ef1d5aaa6f7fa880832498ff9aee7b0ee252846 langs/pl_pl.json
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
@@ -155,6 +156,8 @@ e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/runtim
174c8059625e3315a3d9fefe74c17f78ed850ff25ee15bfdabc148107dd344eb scripts/start_stream_backends.sh
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
efb7a1fde2c74f30cd4a1abb88ae07c1cac7378bb33685dd09d52bb30849daac scripts/tests/dashboard_chart_headers_0335_browser_test.py
eb2c9e5c968938b6fbe4675b547ba1f537519878b9095e1fea133f4d83f96fc8 scripts/tests/dashboard_chart_typography_0337_browser_test.py
@@ -188,26 +191,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
4803e44de39b872a5b3b55f9ca9e81c066ba52cb8f6adb879fd57c5f94b93e89 src/acl/dynamic/tests.rs
63efab1e95d41134fc30cc57320c32810bbae9f81a640bdb24b1be2059e63d8a src/acl/dynamic.rs
a22e5ea6381a1eacafe650be0f5cd6e653773e31f6491a562fe0459a309fb7cc src/acl/tests.rs
55485a513e8c4da2a58daa7abcb15b32ed408a5fc062cf23599fb83d16b90cfe src/acl.rs
63efab1e95d41134fc30cc57320c32810bbae9f81a640bdb24b1be2059e63d8a src/acl/dynamic.rs
4803e44de39b872a5b3b55f9ca9e81c066ba52cb8f6adb879fd57c5f94b93e89 src/acl/dynamic/tests.rs
a22e5ea6381a1eacafe650be0f5cd6e653773e31f6491a562fe0459a309fb7cc src/acl/tests.rs
6f5d3137bf24451da05c1db9f0f9e2a451305db17b6bc2590815ad54a6511829 src/acme.rs
587097620b2f25ce7c6f59f907b47660184b095e3040c7615e74509a82e7cdd8 src/acme/jobs.rs
3f158f46e80b403b658b4e9b1e3710e38c70e47b2d476b8223d9c394ffeeba95 src/acme/model.rs
d1dcb7bdfb7cfdc9c3b88cfb6908201c58532bc888744a13f45853079f214659 src/acme/providers/tests.rs
c5334de6c732d76b2bea7bfc17b3ac305a250bbaad31a5420d6a7d7abb200449 src/acme/providers.rs
d1dcb7bdfb7cfdc9c3b88cfb6908201c58532bc888744a13f45853079f214659 src/acme/providers/tests.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
@@ -221,8 +224,8 @@ d9120f7dc61d27621c5ac27ea49815dc7945c44d5b99d208da503818c198f649 src/admin/oper
92b3e05f04711d8001b56816405859631d9e917868e15be965c7e76ef4ddef99 src/admin/preferences.rs
4a5cb4c3a3aa8b9a519020854506ce4be5e688a6c4298260322ae641dded7fe9 src/admin/preferences_columns.rs
9a3f13e2477769ae680c03d63336880c3b7b7d5aba4a91e44390a976608b9b1d src/admin/preferences_tests.rs
7a00e4ab943f74d2b1bdcbfab7f9cd3f584e9f59536b4aca6c4c31f5f5508d12 src/admin/public/tests.rs
4a7a2017ba57c9e7a8b29e73764d439f6d03653403909ef198ded8c48d152784 src/admin/public.rs
7a00e4ab943f74d2b1bdcbfab7f9cd3f584e9f59536b4aca6c4c31f5f5508d12 src/admin/public/tests.rs
9926827131254677a848c178c80850b3ca1993c05b4bf913687dc0c7992ad048 src/admin/refresh_preferences.rs
daef5e663c1bc99a22b3b8fa4481ae5e4916b91db132c104bc050f51b1fdf77f src/admin/router.rs
a40adfbaa908da3ea726d7aa3d45394a70a6a55b6fc0063270ec4d2a2f2026a3 src/admin/session.rs
@@ -251,7 +254,7 @@ daa20d634f921191cb4ac37889504f203ab39ad7e8fd8f33e1b182fc4206c24d src/admin/web/
86ac42bd4a6a84bb137d5a3c3dc3d1f4e404a543447845e6ea6e6e8210e268ce src/admin/web/app/settings.js
8386710827e5ccc9432fb46413368f94afc1016425404a6092b460903636837e src/admin/web/app/stream-dashboard.js
7f89bfae66e2a08a05a112733864e908f036bb1ee3d8b5b9ff23783b870acd96 src/admin/web/app/web-overview.js
bb94697a1cbeccaf80069dd47ab84efe14b052d81c472f8309478bbeb4e05f06 src/admin/web/cache-panel.js
849b2ca9d183bdf6cb1e04651cda52da55b8a631e11ab1d827a052c8436bbd76 src/admin/web/cache-panel.js
02a9f5a61379e68e1723e64ef4c589500e9a993adcb02fe352f1cf9456c90c52 src/admin/web/certificate-api.js
aec5755244242424e5091ff57c0258d2618588618fe1d52d36f081d03879f686 src/admin/web/dashboard-defaults.json
797e2935a34b974cb0e07a88056c31e6948cc11385bf2a076d902a332882d0a0 src/admin/web/dashboard-widgets.json
@@ -276,7 +279,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
7ffdda1b657c613f2bd3b7a9c7721d67d3d7a159351aaeccbde2a213597b77f7 src/admin/web/index.html
ab762658c3f668a2890d275a869cecb7255352bbd97c8b6b619572627f3196f9 src/admin/web/index.html
03ad9572bb14c34059636d612cf6b876d6361ce601817cff674c57b715dc65dd src/admin/web/integrations.js
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
@@ -299,17 +302,18 @@ 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
b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files/tests.rs
53237ae8b08d261ce21e0aa6fd225bf4f3bb17a37a80529e2a4039d45b13f7c5 src/auth/files.rs
b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files/tests.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
09c3a622a7d2169e49e3369ea5f0f816eab2f717eaf1675c625d518c8f34d367 src/backup.rs
426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs
ca1e03b56a872dca37ed9d05ead41c1538a808e80b09fb9494823c443cd51c61 src/balance/health.rs
59d90e709bfc2be6cd48576f1c1d222891523853a46f2561f0562d74d01f60a5 src/balance/lease_runtime.rs
554b730c39a935b7da46bfb76b474b99cee07f8c482cb0490f2cf81ba7a51cf2 src/balance/model.rs
@@ -317,48 +321,48 @@ ca1e03b56a872dca37ed9d05ead41c1538a808e80b09fb9494823c443cd51c61 src/balance/he
477132dd381e23f1be197c32ff58a0925f41d3e6aa63fe77c10973cefb2b76f6 src/balance/pool_selection.rs
8abb6048303ee8c022546a528d5693167766e7450efd8e20f37ec6fd0fa9ef5e src/balance/pool_view.rs
842318f21a919055522f8bd9dae67bf4ec1c8c32dc52314cc048ba1d54a0b379 src/balance/tests.rs
426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs
9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs
5049d2bd01a9c866f3bad2288d8d594df96bba6045dfe15cd22b4173562b7388 src/basic_auth.rs
9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs
7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.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
284f1cc56113fbcc3b1e617103d75a7b70fd7d43e536d6017c15912154921424 src/cache/dashboard.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
88f6210c03d7a8e7a35be1f123c9c15675af6e6a599c50db927d99866851b634 src/cache/lookup/hot.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
a7b6dfeb63510e4710eb11a4939b360b8863a4b3f71de263d8f3e50dfd006c57 src/cache/model/cache.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
88d74fc1b7b9ca15a54a8f4893ad3993105952148bcfee099e9bc9727b622aa3 src/cache/model/http_invalidation.rs
d890524999dc50aeb1dbda90264b46a1d53f08ac923d831b89ba82e4a564d923 src/cache/model/maintenance.rs
5441fa3a384add28b6fdd15300cf2648f8237dbfc211cad9117754203be76911 src/cache/model/settings.rs
55dcc0ad84d6b73689ba43e759d5685c406419c7733e23e78d5b072891ab17d7 src/cache/model/views.rs
0c8b0d550e94d4474dcdf991aa0340228dc5f2f904f39350e6e666a16f16fe87 src/cache/model.rs
989df11e246123f760ef31689370f3500e0913fcc6b70d7ac0c2c95e2f412fb0 src/cache/policy/request.rs
d426d341a7678f8ec70406d86052c97d0f49804bbcd8402951103d61a735b9da src/cache/policy/response.rs
e26dce02df9c043329ca164fed215d271781bce8fb03240f528331bf3df6b9d7 src/cache/policy.rs
989df11e246123f760ef31689370f3500e0913fcc6b70d7ac0c2c95e2f412fb0 src/cache/policy/request.rs
d6051d55168fe1db97980a54c22c708f4f325ba72bfc36f35a1851eb5dcc051d src/cache/policy/response.rs
7e2bcfbb2b57732cdc704d9df7a9298fb80e735e37984431bc936630e41e7f8a src/cache/range.rs
291d0dcc3a07926eadbc9419b8fc1740b4023ef01c0521269ae2d83f4a5acb6d src/cache/tests/cold_tier_tests.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
ddbcf27a667d779065d7a654d8094910e92958d2b8862a2ec2a85ca41b7f21e5 src/cache/tests/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
4bdbde197d135fee8be20e93427eba8ca7b60b801717ed7ca734319bedb6ef05 src/compression/tests.rs
e0146bf5d2e2e33aedcccd9205e5bd457559c9d77dc7ce266f8c165d1b867f0f src/compression.rs
4bdbde197d135fee8be20e93427eba8ca7b60b801717ed7ca734319bedb6ef05 src/compression/tests.rs
7bc8285cb2012506fd1a38a71dffa561f885f4ab65a9b2a7c8344319e3cd1cf6 src/config.rs
dbd57ef3307b937614cdfdb6c1df82acb80cbbef39f09d19fe2edfc24334aa58 src/config/backend.rs
beb831a208c4e5f7ebe8327686b1e0edc66608c7032f895cb4fc3366b189aae0 src/config/compile.rs
dfa89a9e214d0b33b4efce8fa678f1fec17d17656e203acf84dfd09ce33868e0 src/config/internal_routing.rs
@@ -366,58 +370,58 @@ dfa89a9e214d0b33b4efce8fa678f1fec17d17656e203acf84dfd09ce33868e0 src/config/int
fc993c356386a9ff17379d48a70ef388df5b2b10ca9ee04f06342f68f9dbbaae src/config/routing.rs
a7ccffdeeb258adae41c616022ed0181cf590d708d305d48c63317e4a3f68699 src/config/snapshot.rs
41a4fa4f9a919a8c848ee01d01423d82a136790c9d74f27319c1e5a6d9f18097 src/config/tests.rs
7bc8285cb2012506fd1a38a71dffa561f885f4ab65a9b2a7c8344319e3cd1cf6 src/config.rs
23541660151d9005555a07db6d491455d61b185f0cc70c3ec3a0bee2aef08dc3 src/db.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
b45d6040810c93ae2acb04ef6b0a59c189751edf9008cd81a4521c9e04e7f07d src/docker.rs
057e652f216a1478638cbb53b1c64ac88c7f413a65825caacbf893c5af004a63 src/forwarding/tests.rs
2a02ea2e010cde1b1fe99b2e6baa80ea344ea6b332dbb3c4be795b71541f64a7 src/forwarding.rs
057e652f216a1478638cbb53b1c64ac88c7f413a65825caacbf893c5af004a63 src/forwarding/tests.rs
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.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
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
e4f07bfb7eb75b6897ef812870caff158679a3046d64582428abe8c6df490dbe src/headers/tests.rs
f6ebafafa82391f8cb3b5a09331f3762b6ca5a9fb64bafb5772cde3ab814dfcf src/headers.rs
e4f07bfb7eb75b6897ef812870caff158679a3046d64582428abe8c6df490dbe src/headers/tests.rs
9e21edc67a14ef8a2ce3dda0ed18784d0fd0be305973e5d216058b7ac802c6cb src/http3.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
f5815d778cc1785723faed4abb15f9880f22b6524b5289fe1e7158905aac0986 src/logging/model.rs
42fef80af50735cd566c6801dad86ba81be98cf4a8ec7d45c2ea26f53f556a22 src/logging/runtime.rs
584782215ea78a88526cdb5515031d8d5b87910875e26c0024b93a06680f6791 src/logging/tests.rs
2551189a3e5701371026c9abd2c5d44d37495f28fb67e220180421bfd315e3dd src/logging.rs
3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs
fc8e99eac83c083d65101f6402aacbcc8392d66508c31566a50de3ebcb12e7da src/main/admin_runtime.rs
42a9197475823f67c1ca762951ede5d7a2f10124bd6ed4d1a3365f4297be894c src/main/bootstrap.rs
5a98d0e249338e73aa57ae2e352dc16db2f79c2d042c1544ce1c2022da76a31c src/main/proxy_runtime.rs
0a6578a0f4023b9f8d73043bf942dc5e096ef9afb8c0d2f131c14f65d08ac01d src/main/shutdown/tests.rs
4dd7c1084a6214c0f2513ebf86fe8ef144470695557772bfe25979edd64491ab src/main/shutdown.rs
0a6578a0f4023b9f8d73043bf942dc5e096ef9afb8c0d2f131c14f65d08ac01d src/main/shutdown/tests.rs
e5c2e9189777725f4afa1c3c052e4f75047ec3f2f486630d990f5d3c194bcc96 src/main/tasks.rs
3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs
201ab702722d6d21585fe8bb49939150d61bfac7ed8dc198b98c2cc6098ffb5f src/metrics/global.rs
a54b5b34ea65f29723b89b81bf7968a238e8247b9c9760f3430d3504cb1017c9 src/metrics/vhost/tests.rs
49b11df65b4fb0b5b727f9f867436584680b45e3f4fbd53f74bc0cab8b9a88ec src/metrics/vhost.rs
49d7e104585c8791ac9a203dddd95802784b68c0ba65d6f90f3d57154b4c7871 src/metrics.rs
c915d30bbd7032d867e7916b4cd45f3bb7e7604d3f4558f4383c59394b4300c6 src/not_found/tests.rs
201ab702722d6d21585fe8bb49939150d61bfac7ed8dc198b98c2cc6098ffb5f src/metrics/global.rs
49b11df65b4fb0b5b727f9f867436584680b45e3f4fbd53f74bc0cab8b9a88ec src/metrics/vhost.rs
a54b5b34ea65f29723b89b81bf7968a238e8247b9c9760f3430d3504cb1017c9 src/metrics/vhost/tests.rs
a4702c95c7e5077c2ccff86e2e3216e1da6ff9ee4383ae199a0aff2f78db325f src/not_found.rs
da40e8ece87245efc49a0c4e750526b1d19fd97565856bd5caf641fcc1dfe18d src/pages/tests.rs
c915d30bbd7032d867e7916b4cd45f3bb7e7604d3f4558f4383c59394b4300c6 src/not_found/tests.rs
d66936cc15fd5d56566d40fa8cab93f80d94b05f10ac44478a30f63572a95e32 src/pages.rs
da40e8ece87245efc49a0c4e750526b1d19fd97565856bd5caf641fcc1dfe18d src/pages/tests.rs
0dba7b6a33866cc7bbe05509b01961e545892645d1dad4eb88ac8a4bec65e3bf src/prometheus.rs
cc6fb14351b009a16d07ed0f412c2bcc68cd7e551421aea03d8d97ceec9bee40 src/prometheus/core.rs
c46882ed26742d93806a976257674326e105b8ca1fc23f5ca81e317b268057aa src/prometheus/docker_misc.rs
d3c6b08088df0003144544936a55e2a3661aac85292821b594f00adfd19a2951 src/prometheus/model.rs
@@ -425,13 +429,12 @@ d3c6b08088df0003144544936a55e2a3661aac85292821b594f00adfd19a2951 src/prometheus
028e1dd9f2ec2afaf699a6b21c2d4b0d96e76f11f5e4ed009c233dadf0f3be51 src/prometheus/traffic.rs
e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus/vhosts_backends.rs
fb2d8d45cd2e947f783eb981b37fa418c39fcb327a3af17a648fadea7f37f888 src/prometheus/web_php.rs
0dba7b6a33866cc7bbe05509b01961e545892645d1dad4eb88ac8a4bec65e3bf src/prometheus.rs
f0f3e21350e4fb15fa2ae282da6b290857dcd6716e62b183e9ecb728b7c24c63 src/proxy/body/tests.rs
4c7199c04e6d314e89825f80feaab35a2efb3b893511f70da0cda46ab1d0bc9e src/proxy/body.rs
f0f3e21350e4fb15fa2ae282da6b290857dcd6716e62b183e9ecb728b7c24c63 src/proxy/body/tests.rs
b88d1d7a6837a2a470e5863f1f3247878daee62791fc3d8e72baeb66469bdfbf src/proxy/cache_helpers.rs
aa49c9928bb91e3d5bae0d340d88396a0417b9edec6192d41e1403533319a897 src/proxy/cache_http_invalidation.rs
3404b03096862de1050088f62cdd811ea1ab61d191f902501173107f81f23b9b src/proxy/cache_plan/tests.rs
676c1b325fd8e55bcdd86451c7d1d8b8d347ca00e8da6b3e5b148a58c376a9b4 src/proxy/cache_plan.rs
3404b03096862de1050088f62cdd811ea1ab61d191f902501173107f81f23b9b src/proxy/cache_plan/tests.rs
a8848cbdf85368548b003af2b43823c453866c472af4c7d2ff0ec7da14c44c8d src/proxy/error_fallback.rs
bf2e6bbd64fb45fceed54af0bff1c1e00da22b79c64dd8f5c23d2af0dbab0b7d src/proxy/forward/cache_lookup.rs
cfa941b60b5d167ec9d8e2566ea3342b588d1a9078a6ca711ad6062ddf00fec9 src/proxy/forward/response.rs
@@ -442,123 +445,123 @@ bb24da9c9ab986a9c4d4144d4d1be0f661561d4afc755f298b58bc679b182850 src/proxy/forw
11dccc239ac8a8e9ea06e884c2f2658f86b7807d14a59b8e052bc7dd75e86d63 src/proxy/forward/web_source.rs
facde401ba5bcbafdcea5524f236cfae37eec11144ceeea879e83314c84bc97e src/proxy/forward_entry.rs
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
5cb7522ceb9a9f083a15596ac4898559295e38d1138c80099577211de8a868ec src/proxy/headers/tests.rs
c438066616527a3108f2ad53e3698ae9b2a80961714394cdd0a450c92f99d992 src/proxy/headers.rs
5cb7522ceb9a9f083a15596ac4898559295e38d1138c80099577211de8a868ec src/proxy/headers/tests.rs
3f25429418941eb72c3847125340db74684ad316eb278ecf658953026fd41f4f src/proxy/mod.rs
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
6ff45990f6beb914e89924bec2c41022f3240a14357cf66ec4f8ada414a49767 src/proxy/request_id/tests.rs
c61738672f210e97333c8c89f9b35f8db454cbc1c36955288dfa4cfff2bbbc04 src/proxy/request_id.rs
6ff45990f6beb914e89924bec2c41022f3240a14357cf66ec4f8ada414a49767 src/proxy/request_id/tests.rs
8ad3619359f3befa14ea71ba43c638fea8f4d8a859cd07e4ed505731b8e32e72 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
d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs
2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs
d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.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
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
e7e3a898e2c769b1ef11e58a13c1053104ada93dd694f8a03ea4b647c44c7d69 src/rate_limit/tests.rs
545247eb00b0116efbd4cc913cc251195a55feded1530e0d8d8945abbae78039 src/rate_limit.rs
811c83ae9fd0f5a13d35b5443cd37843f82b9d600bee4bb136ba9b87732a5347 src/redirect/tests.rs
e7e3a898e2c769b1ef11e58a13c1053104ada93dd694f8a03ea4b647c44c7d69 src/rate_limit/tests.rs
d40ad63939097d2b91339127d5835a74b618d83b7ba31db0c512f05a82107d6e src/redirect.rs
811c83ae9fd0f5a13d35b5443cd37843f82b9d600bee4bb136ba9b87732a5347 src/redirect/tests.rs
d0939e1b0829c25356a0b67ecb3a178c74936dd07baa15fee24eb32544db6cad src/runtime.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
d0939e1b0829c25356a0b67ecb3a178c74936dd07baa15fee24eb32544db6cad src/runtime.rs
6dca8da072a3bd610704a391bb6e25c9042971f4ebc60edb47665ce1d74cbc58 src/secret_store/tests.rs
208a378b14ca80e1412ffa19ef1b140594182971e2e797ee0bb95bcdb8404da7 src/secret_store.rs
0e5fc2aff856bf6fd959cba12b5ddd1e3b3cbeb0fbaadde906c811cdfafdaae7 src/security/tests.rs
6dca8da072a3bd610704a391bb6e25c9042971f4ebc60edb47665ce1d74cbc58 src/secret_store/tests.rs
6fa1d8c352bce7a16ff0208bc4cdbe80404715df0aff4ed26910a5df57cbbcd4 src/security.rs
0e5fc2aff856bf6fd959cba12b5ddd1e3b3cbeb0fbaadde906c811cdfafdaae7 src/security/tests.rs
98c52aa3b50d2e211ab752ccaf353c9a8c1500a87f9c1684b5dd0ba9d10e4619 src/server.rs
f2f6196632b2fdb2444189624f961edaf5bdfff57f700b0207b57e2491c1daa5 src/server/http.rs
050c48ea1127aa87dba92c074ab84e8346e65dbe253621ea53f8ca2f68d791ab src/server/http1_connection.rs
8c6ca5eaffb9acab57199d0349722703b903cfff3752baad2cf2596bd51188de src/server/listeners.rs
4424a99b0b688d1119ee25626778b4804aedfaa0799a4a9bc0ee99bc5c7ba96b src/server/sendfile.rs
e9e478cf45b88994eaffd115f51764b6b07852ee3c941cafdabb20584d3dabc0 src/server/serve/tests.rs
71899a9b1897fa22ab9e8eb6a670054a6d382bad78714006b9b218d07ba99902 src/server/serve.rs
e9e478cf45b88994eaffd115f51764b6b07852ee3c941cafdabb20584d3dabc0 src/server/serve/tests.rs
960a82e02f09d75141fe3c1eecca915b66f17fdc8167196cd2fe21d5e6859376 src/server/tls_runtime.rs
98c52aa3b50d2e211ab752ccaf353c9a8c1500a87f9c1684b5dd0ba9d10e4619 src/server.rs
73ec453ac2850f6963f5bc33401cb8c92aa26d5123f15d68e6633860d91f265e src/settings.rs
240ecdd70e8662ff3b84c21830dc0cbbbacadd755d56146e54214bc2887f068c src/settings/public_origin.rs
1f1121c3562c8f8f3fdee2113be2afffb8849be2d303e4c4409f87478b6e3307 src/settings/tests.rs
73ec453ac2850f6963f5bc33401cb8c92aa26d5123f15d68e6633860d91f265e src/settings.rs
9dbf814870b8636770f579c3d36d16f5b6afe8bc66adfad356c26d7ae7dbce45 src/stream.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
9dbf814870b8636770f579c3d36d16f5b6afe8bc66adfad356c26d7ae7dbce45 src/stream.rs
d9123c623e2a97d09e41edaa29993b4b2c8cba39a3abdf2b48a205aa9a9c2939 src/system_stats/linux/tests.rs
3070d78a7b88c2e47ed59960fa35e7d220a4cd7d78e48f6c6d2bcc5ff77f9620 src/system_stats/linux.rs
d9123c623e2a97d09e41edaa29993b4b2c8cba39a3abdf2b48a205aa9a9c2939 src/system_stats/linux/tests.rs
751d59582fb92c84e0c0cc7c0855c9d0a545805c9ba3be61826e50fb96c297ed src/system_stats/mod.rs
050aa831b1012c4aaade63863f433f01bfacf5d3f95787e57975572116a30ab2 src/system_stats/tests.rs
0e1d87554db55e724f30c41ec4a5c80bdb8fc9c8cba8700ac509864d4abf8871 src/tinyauth/tests.rs
7467d83066bb6986d8c9103597f7a7617ac677579ed46d573c573ff6ca57bbd9 src/tinyauth.rs
0e1d87554db55e724f30c41ec4a5c80bdb8fc9c8cba8700ac509864d4abf8871 src/tinyauth/tests.rs
1084cccd9de28acd6cb0f0813de7c0953910f51726f73f4acdb2e3ada4593976 src/tls.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
1084cccd9de28acd6cb0f0813de7c0953910f51726f73f4acdb2e3ada4593976 src/tls.rs
36301bcbd20b557dd3d58e77a9175f3581947f577c0bb0b972899cec6786b03f src/traffic/tests.rs
c2f802d95da4332fb8480e83f0c7502334457b3408141c1c4516c9ba320a0463 src/traffic.rs
36301bcbd20b557dd3d58e77a9175f3581947f577c0bb0b972899cec6786b03f src/traffic/tests.rs
d88c92f5a2a7773bc3e715d24039db35da4df53881f1a0859eec819b96f06d3b src/transform/apply.rs
87f8af5261d79072f8215f1fb8a90715a58e7626544fd8176ce840e3111bdb92 src/transform/compile.rs
b2c5236589093454ec60984b785ad6a4af6149d47f6db37d8e08c17fec7a2c1b src/transform/css.rs
aa99b0d3029ef92c78371cb90e11f2026cce890d1be8e10ebd7fa9bc5cb13287 src/transform/html/tests.rs
5478670cef87777b14a2dab308f5a9182df56d7ea2f5625bdc94aa7e3d661503 src/transform/html.rs
cf5195ab6bacccae0082da7b3f1bd0382b73fdeb3607f4ae569ca6daab9d10d0 src/transform/js/tests.rs
aa99b0d3029ef92c78371cb90e11f2026cce890d1be8e10ebd7fa9bc5cb13287 src/transform/html/tests.rs
a1d2fec07617c8a8dc425bfaeaf77c844152a0a174568a88a5baf4c317cc6c5c src/transform/js.rs
216e24e29ea87be4a124f95f898e155f127fd959d7d5d9b30f8cd0b2b5018f30 src/transform/json/tests.rs
cf5195ab6bacccae0082da7b3f1bd0382b73fdeb3607f4ae569ca6daab9d10d0 src/transform/js/tests.rs
226ae561dc7b0243c04e55e28cdfd2b76acd48cf614d1919b81c196660b03cfb src/transform/json.rs
216e24e29ea87be4a124f95f898e155f127fd959d7d5d9b30f8cd0b2b5018f30 src/transform/json/tests.rs
6a8c28be93fb590942493c075b29ace1527981542bd183f232aa82b62e951eca src/transform/mod.rs
fde2fa3aa94b30d8536afec1c11348cfbd922d0b5ff89e91d34266e8e3863e26 src/transform/model.rs
0f53c64f442390ee20a59087a10f8f9ffb58053263e9aa848ddabfae1a6f3f51 src/transform/svg/tests.rs
a4e1d2aa2913587c250f251356431097b76995aee6e8bf9e35c4978a95491de1 src/transform/svg.rs
0f53c64f442390ee20a59087a10f8f9ffb58053263e9aa848ddabfae1a6f3f51 src/transform/svg/tests.rs
d1f3f53fedae0d8ef69eeee457f7d30ed5a670a1ed4ced60683400ee46680260 src/transform/tests.rs
c42d000d1d576721af3ffe930589388693001b8113554a7658f6dcbc665b5d1b src/update_check/tests.rs
6146c59f36cf419623747d497b3de685da6c84964df43317bebdcb21deb9ee09 src/update_check.rs
c42d000d1d576721af3ffe930589388693001b8113554a7658f6dcbc665b5d1b src/update_check/tests.rs
92efcdaafb9f5a8514daf63d7fcde29be977d7bdcd90162eda0ce00d6967ffbd src/upstream.rs
ca695e246bee5389b3757fe8588e48bff5d0ad473bc8588ad4ba7b52c99d7dfa src/upstream/client.rs
ad29f3fc15c9b627b5d752af5adc9f99ee1364fec2df63f1e4509cb223988efb src/upstream/client_tests.rs
75fef0a488d3f3dabfa03c2eabc5995442dfb3c9a1fde8eb6653a124dda25d43 src/upstream/protocol_tests.rs
8a112154b9631f62a54584302a3ff6a64e4f177ce79aba678b4cb23e466c6ea3 src/upstream/tests.rs
92efcdaafb9f5a8514daf63d7fcde29be977d7bdcd90162eda0ce00d6967ffbd src/upstream.rs
6d7cb73de7d8c6f3780736480f5f81ef08813f4626fd5ed494aca0552cf214a1 src/user_agent/tests.rs
a6b4905c245a22eafb1aa535d303d0fa24e4353db94e1d67fd42b825bf8c5adb src/user_agent.rs
6d7cb73de7d8c6f3780736480f5f81ef08813f4626fd5ed494aca0552cf214a1 src/user_agent/tests.rs
3aa1b07cd26d4a5b9e5e94d1d6a712ef1397d827e7f1e8c01f7195b98d2c5f67 src/vhost.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
3aa1b07cd26d4a5b9e5e94d1d6a712ef1397d827e7f1e8c01f7195b98d2c5f67 src/vhost.rs
8d1eff4b24f01d137414ef5419f75dd281879228adb70409645b11b3c78c20e9 src/web_server/config.rs
9744e8c29ae72a4d9fbc9a76166a97f8292c4c312295ea40d1cb699e03d80a9d src/web_server/config/compile.rs
bcaa1ae15134d85668b05a88d8abea01e5d188f535ad83cd4459732fd56f2f44 src/web_server/config/model.rs
a863ad1337fbc353c0f42caea4ac8b3644792e2aa08c37f992b7882b4b8dc6d5 src/web_server/config/validation.rs
8d1eff4b24f01d137414ef5419f75dd281879228adb70409645b11b3c78c20e9 src/web_server/config.rs
86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.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
833c9fea744b9649046e1a64d6fc6972fbf6cea640e1943b0db52b7ccfa9faad src/web_server/response/file.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
833c9fea744b9649046e1a64d6fc6972fbf6cea640e1943b0db52b7ccfa9faad src/web_server/response/file.rs
ba793a140699956a9aef6bc79b0c731c481af8ab7ac3d816a28695743febcb30 src/web_server/response/file_body.rs
c2e54fd803a10f74490f183e8c030fd0e3e72f5de4e79c55bc393ab57d90ab9a src/web_server/response/file_body/tests.rs
20509f9302b79d3a7be76dda6edaf5711826d3ddf47d19ebe07cb289427ca328 src/web_server/response/headers.rs
305c0109b4f869a99381a142853f9e2205f5c7c4f904b2dfa4643274bfc542af src/web_server/routing.rs
400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs
54cf72a82e023fe46513b9cf3130adfef1cd96c40f790c533016b4825d542e06 src/web_server/serve.rs
+93
View File
@@ -0,0 +1,93 @@
# RustProxy 0.34.0 — poprawki cache, transformacji i kontrola RPS
Status: **zmodyfikowane źródła, niekompilowane lokalnie** (brak `cargo` i `rustc`). Numer wersji: 0.34.0.
## Co odtworzono na binarce użytkownika
Uruchomiono binarkę Linux ELF w izolowanej konfiguracji z lokalnym origin HTTP, z włączonym cache, transformacją CSS, `max_ttl_seconds=86400`, diagnostyką i API administracyjnym. HTTP/1.1 localhost, pełne GET, brak kontaktu z witryną produkcyjną.
| Odpowiedź | Pierwszy GET | Drugi GET | Liczba pobrań z origin |
|---|---|---|---|
| `/normal.css` | MISS | HIT | 1 |
| `/aged.css` | MISS | MISS | 2 |
| `/br.css` | MISS | HIT | 1 |
| `/aged-br.css` | MISS | MISS | 2 |
| `/private.css` | MISS | MISS | 2 |
| `/no-store.css` | MISS | MISS | 2 |
Zwykły CSS i skompresowany Brotli dostają HIT. Warianty z `Age: 1553483` mają `MISS → MISS` mimo `Cache-Control: public, max-age=2592000`. Przypadki `private` / `no-store` słusznie nie trafiają do cache. Zmiana keep-alive vhosta nie usuwała poprawnie zachowanego obiektu. `expose_only_hits=true` celowo ukrywa nagłówki na MISS.
## Główna przyczyna i poprawka
W `src/cache/policy/response.rs` obowiązywał wzór `ttl = min(origin_max_age, local_max_ttl)` oraz `remaining = ttl - corrected_age`. To jest sprzeczne z zadaniem lokalnego limitu: świeżość w origin nadal trwa, ale CDN `Age > local_max_ttl` dawał sztuczne 0 sekund lokalnej świeżości.
Poprawiony wzór:
```text
corrected_initial_age = max(apparent_age, upstream_age + response_delay)
remaining_origin_freshness = max(0, origin_max_age - corrected_initial_age)
remaining_local_freshness = min(remaining_origin_freshness, local_max_ttl)
entry_fresh_until_age = corrected_initial_age + remaining_local_freshness
```
Przykład z przesłanych nagłówków: wiek upstream 1 553 483 s = prawie 18 dni. Pierwotna świeżość 2 592 000 s (30 dni), więc pozostało około 12 dni w origin. Po naprawie RustProxy będzie mógł przechować świeżą odpowiedź przez lokalny limit (domyślnie 86 400 s = 1 dzień). Nagłówek Age nadal będzie około 1,55 mln sekund i będzie rósł na HIT — to poprawne.
`Age` nie wynika z `Last-Modified: 2016`. Najczęściej jest przekazany przez upstream/CDN, a stara data `Date` też mogłaby podnieść apparent_age. Rozróżnienie: `Age` = HTTP wiek całkowity; `resident_age_seconds` = pobyt w HOT/COLD.
## HOT RPS i panel
1. HOT HIT nie wyjmuje już i nie wkłada od nowa wpisu oraz indeksu `Vary`, tylko aktualizuje licznik HIT i kolejność LRU w miejscu. Zmiana ma zmniejszyć alokacje i krytyczną sekcję; **zysk RPS jeszcze niezmierzony**.
2. Panel Cache ma osobne kolumny **Age** i **Czas w RustProxy**. API `dashboard.objects[]` dodaje `resident_age_seconds` także dla COLD. Pozostałe nazwy pól nie zostały zmienione.
3. Licznik `x-cache-hits` dotyczy konkretnego aktualnego wpisu. Gdy obiekt zostanie wyparte z HOT (limit wpisów/pamięć), usunięty lub zastąpiony po MISS, licznik restartuje. W próbce użytkownika TTL wynosił około 604 tys. s (7 dni), więc późniejszy MISS po ~4 minutach **nie wynikał z normalnego wygaśnięcia TTL**. Konkretnej przyczyny epizodu nie można ustalić bez trendów `evictions`, `purges`, `hot_demotions`, `cold_hits`, kluczy/Vary oraz zmian konfiguracji.
4. Brak `x-cache` na MISS jest oczekiwany, gdy włączono **nagłówki diagnostyczne tylko na HIT**. Brak wpisu w API może mieć inne przyczyny (np. cache-control `private`, `no-store`, brak polityki origin, limit obiektu) i nie należy ich obchodzić na siłę.
5. Nie zmieniano uprawnień, `private`/`no-store`, kluczy Vary, TTL po stronie origin ani zasad selektywnej invalidacji.
## Wynik symulacji TTL
Wykonano **100 000 deterministycznych scenariuszy**. Dla każdego sprawdzono: po naprawie lokalna świeżość nie przekracza `local_max_ttl` ani świeżości pozostałej w origin.
| Scenariusz | Stary pozostały TTL | Po poprawce (symulacja) |
|---|---:|---:|
| Age = 1 553 483, local cap 1 dzień | 0 s | 86 400 s |
| Age = 1 553 483, local cap 7 dni | 0 s | 604 800 s |
| Origin kończy świeżość za 10 s | 0 s | 10 s |
| Origin jest już nieświeży | 0 s | 0 s |
W symulacji 33 387 / 100 000 losowych przypadków stara polityka błędnie dawała zero zamiast dodatniego TTL. **To nie jest procent regresji w realnym ruchu**, a wynik losowania parametrów testowych.
## Nginx — rzeczywiste pomiary starej binarki
Archiwalne wyniki samej binarki `rust-proxy 0.34.0` na tym środowisku (Linux, 4 vCPU, HTTP/1.1 loopback, ApacheBench, 64 połączenia, cache OFF, 3 powtórzenia), z nginx 1.26.3. Brak miarodajnego benchmarku **nowych źródeł** przed ponowną kompilacją.
| Endpoint | RustProxy RPS (mediana) | nginx RPS (mediana) | RustProxy / nginx |
|---|---:|---:|---:|
| proxy 1 KiB | 10,544 | 58,709 | 18.0% |
| proxy 64 KiB | 7,893 | 17,358 | 45.5% |
| proxy 1 MiB | 1,010 | 1,666 | 60.6% |
| www 1 KiB | 11,984 | 71,318 | 16.8% |
| www 64 KiB | 9,224 | 26,600 | 34.7% |
| www 1 MiB | 1,264 | 1,098 | 115.2% |
Ograniczenia porównania: ApacheBench może limitować klienta; wyniki nie reprezentują TLS/H2/H3 ani ruchu produkcyjnego. **Nie wolno tych RPS przypisywać nowej rewizji.**
## Zestaw testów dla nowej kompilacji
```bash
CARGO_INCREMENTAL=0 cargo test --locked --lib
CARGO_INCREMENTAL=0 cargo build --release --locked
python3 scripts/tests/cache_age_policy_034_model.py
python3 scripts/tests/cache_age_transform_034_live.py --binary target/release/rust-proxy --output /tmp/cache-test-034
python3 scripts/tests/cache_scope_034_static_test.py
python3 scripts/tests/proxy_hotpath_034_static_test.py
python3 scripts/tests/sendfile_034_static_test.py
```
Do testów jednostkowych dodano Age, transformowane CSS/HOT, democję do COLD i odczyt COLD po restarcie, logikę LRU/HIT oraz kontrolę zakazów `private`/`no-store`. Test lokalny sprawdza CSS/Brotli, origin count, API, keep-alive config i ukryte nagłówki. Test po kompilacji bez przełącznika `--expect-old-age-bug` ma wykazać `MISS → HIT` dla Age/Brotli. Na starej binarce odtwarzanie regresji przechodzi z `--expect-old-age-bug`.
Kontrole wykonane na źródłach: źródłowe testy cache scope, proxy hotpath, sendfile, składnia JS, parsowanie tłumaczeń. **Nie uruchomiono testów Rust ani benchmarków poprawionej binarki** — środowisko nie ma `cargo/rustc`.
## Następny pomiar po kompilacji
Porównać tę rewizję 0.34.0 z poprzednią 0.34.0 oraz nginx dla 1 KiB / 64 KiB / 1 MiB; osobno cache OFF, HOT HIT i transformacja/Brotli. Raportować RPS, p95/p99, procesor, RSS, nowe TCP, `cache.evictions` i `cache.purges`; nie modyfikować ustawień cache podczas benchmarku.
Uwaga: `curl -I -X GET` miesza zachowanie HEAD i GET; do testów reprezentacji zalecane `curl -sS -D - -o /dev/null URL`.
+3 -2
View File
@@ -619,12 +619,13 @@
"cacheObjectList": "Stored objects",
"cachePrevious": "Previous",
"cacheNext": "Next",
"cacheObjectHint": "Most recently used first. Click Target or Cache key / tags to copy the full value. Bodies and request headers are not exposed.",
"cacheObjectHint": "Most recently used first. HTTP Age includes upstream/CDN time; Time in RustProxy is the actual HOT/COLD residency. Stale retained objects can also appear. Click Target or Cache key / tags to copy the full value.",
"cachePath": "Path",
"cacheBodySize": "Body",
"cacheAge": "Age",
"cacheResidentAgeValue": "Time in RustProxy",
"cacheTtlRemaining": "Remaining TTL",
"cacheNoObjects": "No fresh objects in this scope. Inspect the decision counters above.",
"cacheNoObjects": "No stored objects in this scope. Check the vhost filter and decision counters.",
"cacheLegacy": "Unassigned",
"cacheGlobalLimit": "Global limit",
"cacheFills": "Active fills (global)",
+3 -2
View File
@@ -619,12 +619,13 @@
"cacheObjectList": "Zapisane obiekty",
"cachePrevious": "Poprzednie",
"cacheNext": "Następne",
"cacheObjectHint": "Najpierw ostatnio używane. Kliknij Target albo Cache key / tags, aby skopiować pełną wartość. Treści i nagłówki żądań nie są ujawniane.",
"cacheObjectHint": "Najpierw ostatnio używane. Wiek HTTP (Age) obejmuje także czas w upstream/CDN; Czas w RustProxy to rzeczywista rezydencja w HOT/COLD. Wpisy nieświeże mogą nadal być widoczne. Kliknij Target lub Cache key / tags, aby skopiować wartość.",
"cachePath": "Ścieżka",
"cacheBodySize": "Treść",
"cacheAge": "Wiek",
"cacheResidentAgeValue": "Czas w RustProxy",
"cacheTtlRemaining": "Pozostały TTL",
"cacheNoObjects": "Brak świeżych obiektów w tym zakresie. Sprawdź powyższe liczniki decyzji.",
"cacheNoObjects": "Brak zapisanych obiektów w tym zakresie. Sprawdź filtry vhosta oraz liczniki decyzji.",
"cacheLegacy": "Nieprzypisane",
"cacheGlobalLimit": "Limit globalny",
"cacheFills": "Aktywne zapisy (globalnie)",
View File
File mode changed.
View File
File mode changed.
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Deterministic oracle for HTTP Age / local freshness-cap algebra.
No binary execution, no claim of measured throughput. Run with Python 3 only.
"""
import json
import random
from pathlib import Path
def old_freshness(max_age, header_age, apparent_age, response_delay, local_limit):
age = max(header_age, apparent_age) + response_delay
return max(0, min(max_age, local_limit) - age)
def new_freshness(max_age, header_age, apparent_age, response_delay, local_limit):
# RFC 9111 corrected_initial_age = max(apparent_age, age_value + response_delay)
age = max(apparent_age, header_age + response_delay)
lifetime = min(max_age, age + local_limit)
return max(0, lifetime - age)
def run():
cases = [
('user_cdn_example', 2_592_000, 1_553_483, 0, 0, 86_400),
('user_cdn_example_7d', 2_592_000, 1_553_483, 0, 0, 604_800),
('origin_nearly_stale', 2_592_000, 2_591_990, 0, 0, 86_400),
('origin_stale', 2_592_000, 2_592_010, 0, 0, 86_400),
('normal_small', 3_600, 0, 0, 0, 86_400),
('date_is_older', 3_600, 10, 600, 2, 86_400),
('rfc_delay_case', 3_600, 3, 10, 2, 86_400),
]
report = {'reference': 'RFC 9111 4.2.3 (Age) and 4.2.1 (freshness lifetime)',
'kind': 'algebra simulation; not RustProxy RPS benchmark',
'scenarios': [], 'randomized_checks': 0, 'false_miss_cases_old': 0}
for name, max_age, age, apparent, delay, local in cases:
old = old_freshness(max_age, age, apparent, delay, local)
new = new_freshness(max_age, age, apparent, delay, local)
report['scenarios'].append({'scenario': name, 'origin_max_age_seconds': max_age,
'received_age_seconds': age, 'apparent_age_seconds': apparent,
'response_delay_seconds': delay, 'local_max_ttl_seconds': local,
'old_freshness_seconds': old, 'new_freshness_seconds': new})
rng = random.Random(0x3400)
for _ in range(100_000):
origin = rng.randrange(0, 1 << 24)
header_age = rng.randrange(0, 1 << 24)
apparent = rng.randrange(0, 1 << 24)
delay = rng.randrange(0, 300)
local = rng.randrange(0, 604_801)
corrected_age = max(apparent, header_age + delay)
new = new_freshness(origin, header_age, apparent, delay, local)
expected = min(max(0, origin - corrected_age), local)
assert new == expected, (origin, header_age, apparent, delay, local, new, expected)
assert new <= local and new <= max(0, origin - corrected_age)
old = old_freshness(origin, header_age, apparent, delay, local)
report['false_miss_cases_old'] += old == 0 < new
report['randomized_checks'] += 1
return report
if __name__ == '__main__':
report = run()
print(json.dumps(report, ensure_ascii=False, indent=2))
+193
View File
@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""Loopback integration regression: transformed Age, HOT/COLD, diagnostics, isolation.
Run after compiling:
python3 scripts/tests/cache_age_transform_034_live.py --binary target/release/rust-proxy
To reproduce the original regression with the user-supplied 0.34.0 binary:
python3 scripts/tests/cache_age_transform_034_live.py --binary /path/to/old/rust-proxy --expect-old-age-bug
No internet and no user data; test config/data/passwords are in a disposable temporary dir.
"""
import argparse
import collections
import copy
import json
import sys
import threading
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'performance'))
from common import Fixture, http, sha256, write_json
BODY = b'/* test cache */\nbody { color: red; padding: 0px; }\n'
# Fixed sample from Brotli encoder, embedded to avoid Python dependency on brotli.
BROTLI_BODY = b"\x1b3\x00\xe8\x1d\x07\xee\xbe\r\xa2\xbc3'\x8cP\xe9\xad\xa0\xf2\xb2\xa7\xe4\xfa\x12\x84\xac\x0c\x1cr\xc0\xfe\xc0>\x0fR*a\x9c\x9e2\xe8\x08\xcb\x1cP\x881\xac\xdd\x88s5(\x9d\x01"
COUNTS = collections.Counter()
class Origin(BaseHTTPRequestHandler):
def log_message(self, *_):
pass
def do_GET(self):
path = self.path.split('?', 1)[0]
if path not in {'/aged.css', '/normal.css', '/private.css', '/no-store.css', '/other.css', '/br.css', '/aged-br.css'}:
self.send_error(404)
return
COUNTS[path] += 1
self.send_response(200)
if path in ('/aged.css', '/aged-br.css'):
policy = 'public, max-age=2592000'
elif path == '/private.css':
policy = 'private, max-age=2592000'
elif path == '/no-store.css':
policy = 'no-store'
else:
policy = 'public, max-age=3600'
self.send_header('Cache-Control', policy)
self.send_header('Content-Type', 'text/css')
payload = BROTLI_BODY if path in ('/br.css', '/aged-br.css') else BODY
self.send_header('Content-Length', str(len(payload)))
if path in ('/br.css', '/aged-br.css'):
self.send_header('Content-Encoding', 'br')
self.send_header('ETag', '"origin-css"')
self.send_header('Last-Modified', 'Thu, 07 Jan 2016 20:04:59 GMT')
self.send_header('Vary', 'Accept-Encoding')
if path in ('/aged.css', '/aged-br.css'):
self.send_header('Age', '1553483')
self.end_headers()
self.wfile.write(payload)
def request(port, path, encoding='identity'):
status, headers, body = http(port, path, headers={'Accept-Encoding': encoding})
return {'status': status, 'x_cache': headers.get('x-cache'),
'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]}
def dash(rust):
status, _, body = rust.admin('/api/cache?limit=200')
assert status == 200, ('cache dashboard', status, body[:200])
data = json.loads(body)['dashboard']
return {'objects': [{k: row.get(k) for k in ('vhost','path','tier','transformed',
'hits','age_seconds','resident_age_seconds','remaining_ttl_seconds')} for row in data['objects']],
'domains': data['domains']}
def find(d, path):
matches = [item for item in d['objects'] if item['path'] == path and item['vhost'] == 'reverse']
assert len(matches) == 1, (path, matches)
return matches[0]
def run(binary: Path, output: Path, expect_old_age_bug: bool):
outcome = {'binary_sha256': sha256(binary), 'expect_old_age_bug': expect_old_age_bug,
'tests': [], 'passed': False}
output.mkdir(parents=True, exist_ok=True)
with Fixture(output, workers=2) as fixture:
origin = ThreadingHTTPServer(('127.0.0.1', fixture.origin_port), Origin)
thread = threading.Thread(target=origin.serve_forever, daemon=True)
thread.start()
try:
rust = fixture.rust(binary, 'cache-age-034')
rust.login()
cfg = copy.deepcopy(rust.base)
cfg['cache'] = {'enabled': True, 'max_ttl_seconds': 86_400,
'expose_status': True, 'expose_hits': True,
'expose_cache_status': True, 'expose_resident_age': True}
cfg['transform'] = {'enabled': True, 'css': True}
for vhost in cfg['vhosts']:
vhost['transform'] = {'enabled': True, 'css': True}
rust.apply(cfg, 'fresh-cache-transform')
for path in ('/normal.css', '/aged.css', '/br.css', '/aged-br.css', '/private.css', '/no-store.css'):
before = COUNTS[path]
encoding = 'br' if path in ('/br.css', '/aged-br.css') else 'identity'
first, second = request(rust.reverse_port, path, encoding), request(rust.reverse_port, path, encoding)
row = {'path': path, 'first': first, 'second': second,
'origin_delta': COUNTS[path] - before}
outcome['tests'].append(row)
if path in ('/aged.css', '/aged-br.css') and expect_old_age_bug:
assert first['x_cache'] == second['x_cache'] == 'MISS', row
assert row['origin_delta'] == 2, row
elif path in ('/normal.css', '/aged.css', '/br.css', '/aged-br.css'):
assert first['x_cache'] == 'MISS' and second['x_cache'] == 'HIT', row
assert row['origin_delta'] == 1, row
assert second['cache_status'] and 'transformed' in second['cache_status'], row
assert first['etag'] == second['etag'] and first['body'] == second['body'], row
else:
assert first['x_cache'] == second['x_cache'] == 'MISS', row
print(path, first['x_cache'], second['x_cache'], 'origin+', row['origin_delta'], flush=True)
snapshot = dash(rust)
outcome['tests'].append({'dashboard': snapshot})
assert find(snapshot, '/normal.css')['transformed'], snapshot
aged = find(snapshot, '/aged.css')
if expect_old_age_bug:
assert aged['remaining_ttl_seconds'] == 0, aged
else:
assert aged['transformed'] and aged['remaining_ttl_seconds'] >= 86_000, aged
assert aged['age_seconds'] >= 1_553_483, aged
assert aged['resident_age_seconds'] < 10, aged
aged_br = find(snapshot, '/aged-br.css')
assert aged_br['transformed'] and aged_br['remaining_ttl_seconds'] >= 86_000, aged_br
# Operational LB tuning must not invalidate a cached representation.
before = COUNTS['/normal.css']
cfg['vhosts'][0]['load_balancing']['keepalive'] = {'max_idle_per_host': 16}
rust.apply(cfg, 'only-backend-keepalive-changed')
normal = request(rust.reverse_port, '/normal.css')
assert normal['x_cache'] == 'HIT' and COUNTS['/normal.css'] == before
outcome['tests'].append({'keepalive_update_without_purge': normal})
# Diagnostics can be intentionally hidden on MISS and remain visible on HIT.
cfg['cache']['expose_only_hits'] = True
rust.apply(cfg, 'only-show-hit-headers')
before = COUNTS['/other.css']
first, second = request(rust.reverse_port, '/other.css'), request(rust.reverse_port, '/other.css')
assert first['x_cache'] is None and second['x_cache'] == 'HIT'
assert COUNTS['/other.css'] - before == 1
outcome['tests'].append({'only_hits': [first, second]})
if not expect_old_age_bug:
# Cold path on a 1-entry HOT tier: demotion and on-disk reuse.
cfg['cache'].update({'max_entries': 1, 'cold_enabled': True, 'cold_on_lru': True,
'cold_path': 'cache/age-regression-cold'})
cfg['cache'].pop('expose_only_hits', None)
rust.apply(cfg, 'cold-enabled-one-hot-entry')
assert request(rust.reverse_port, '/aged.css')['x_cache'] == 'MISS'
assert request(rust.reverse_port, '/aged.css')['x_cache'] == 'HIT'
assert request(rust.reverse_port, '/other.css')['x_cache'] == 'MISS'
deadline = time.monotonic() + 4
while True:
snapshot = dash(rust)
matches = [x for x in snapshot['objects'] if x['path'] == '/aged.css' and x['tier'] == 'cold']
if matches:
assert matches[0]['remaining_ttl_seconds'] >= 86_000, matches
break
if time.monotonic() > deadline:
raise AssertionError('aged transformed CSS never demoted to COLD: ' + str(snapshot))
time.sleep(.05)
cold_hit = request(rust.reverse_port, '/aged.css')
assert cold_hit['x_cache'] == 'HIT' and 'transformed' in cold_hit['cache_status'], cold_hit
outcome['tests'].append({'cold': {'row': matches[0], 'hit': cold_hit}})
outcome['passed'] = True
finally:
origin.shutdown()
origin.server_close()
write_json(output / 'cache-age-transform-result.json', outcome)
return outcome
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--binary', type=Path, required=True)
parser.add_argument('--output', type=Path, default=Path('/tmp/rustproxy-cache-age-034-live'))
parser.add_argument('--expect-old-age-bug', action='store_true')
args = parser.parse_args()
try:
report = run(args.binary.resolve(), args.output.resolve(), args.expect_old_age_bug)
except Exception as error:
print('FAIL', type(error).__name__, str(error)[:1000], file=sys.stderr)
raise
print('PASS', len(report['tests']), 'scenarios; see', args.output / 'cache-age-transform-result.json')
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
+1 -1
View File
@@ -174,7 +174,7 @@
tr.append(target);
const keyTags = `${row.cache_key || "-"}${row.tags?.length ? ` · ${row.tags.join(", ")}` : ""}`;
tr.append(makeCopyCell(keyTags));
for (const value of [row.tier === "cold" ? t("cacheCold") : t("cacheHot"), fmtBytes(body), fmtBytes(storage), fmtBytes(Math.max(0, storage - body)), number(row.hits), `${number(row.age_seconds)} s`, `${number(row.remaining_ttl_seconds)} s`]) tr.append(el("td", value));
for (const value of [row.tier === "cold" ? t("cacheCold") : t("cacheHot"), fmtBytes(body), fmtBytes(storage), fmtBytes(Math.max(0, storage - body)), number(row.hits), `${number(row.age_seconds)} s`, `${number(row.resident_age_seconds)} s`, `${number(row.remaining_ttl_seconds)} s`]) tr.append(el("td", value));
const action = el("td"), button = el("button", t("cachePurgeUrl"), "quiet"); button.type = "button"; button.addEventListener("click", () => void purgeObject(row)); action.append(button); tr.append(action); return tr;
}));
const bans = data.bans || [];
+1
View File
@@ -1413,6 +1413,7 @@
<th data-i18n="cacheOverhead">Overhead</th>
<th>HIT</th>
<th data-i18n="cacheAge">Age</th>
<th data-i18n="cacheResidentAgeValue">Time in RustProxy</th>
<th data-i18n="cacheTtlRemaining">Remaining TTL</th>
<th data-i18n="actions">Actions</th>
</tr>
+2
View File
@@ -362,6 +362,7 @@ impl Cache {
tier: "hot",
hits: e.hits,
age_seconds: age,
resident_age_seconds: hot_resident_age(e).as_secs(),
remaining_ttl_seconds: e.ttl.saturating_sub(age),
});
}
@@ -408,6 +409,7 @@ impl Cache {
tier: "cold",
hits: e.hits,
age_seconds: age,
resident_age_seconds: cold_resident_age(e),
remaining_ttl_seconds: e.ttl.saturating_sub(age),
});
}
+52 -43
View File
@@ -18,10 +18,15 @@ impl Cache {
return None;
}
if let Some(storage_key) = matching_hot_key(&store, ticket) {
let Some(mut entry) = store.remove(&storage_key) else {
return None;
};
if self.hot_entry_banned(&storage_key, &entry) {
// 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)
.is_some_and(|entry| self.hot_entry_banned(&storage_key, entry));
if banned {
store.remove(&storage_key);
if count_miss {
self.counters.misses.fetch_add(1, Ordering::Relaxed);
}
@@ -29,7 +34,10 @@ impl Cache {
release_allocator_memory();
return None;
}
let resident_age_seconds = hot_resident_age(&entry).as_secs();
let Some(entry) = store.entries.get(&storage_key) else {
return None;
};
let resident_age_seconds = hot_resident_age(entry).as_secs();
let age = entry
.initial_age
.saturating_add(entry.received.elapsed())
@@ -44,49 +52,50 @@ impl Cache {
&ticket.request_headers,
);
if state.servable() {
entry.hits = entry.hits.saturating_add(1);
entry.last_access = Instant::now();
entry.sequence = store.next_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(),
let sequence = store.next_sequence();
let (old_sequence, scope, domain, body_bytes, result) = {
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)
};
if let Some(c) = store.domain(&entry.scope, &entry.domain) {
c.saved_body_bytes = c.saved_body_bytes.saturating_add(entry.body.len() as u64);
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);
}
store.bytes = store.bytes.saturating_add(entry.cost);
store.logical_bytes = store.logical_bytes.saturating_add(entry.body.len());
store.lru.insert(entry.sequence, storage_key);
store.index_variant(entry.base_key, storage_key);
store.entries.insert(storage_key, entry);
self.counters.hits.fetch_add(1, Ordering::Relaxed);
return Some(result);
}
store.bytes = store.bytes.saturating_add(entry.cost);
store.logical_bytes = store.logical_bytes.saturating_add(entry.body.len());
store.lru.insert(entry.sequence, storage_key);
store.index_variant(entry.base_key, storage_key);
store.entries.insert(storage_key, entry);
}
if count_miss {
self.counters.misses.fetch_add(1, Ordering::Relaxed);
+3
View File
@@ -28,7 +28,10 @@ pub struct ObjectView {
pub storage_bytes: u64,
pub tier: &'static str,
pub hits: u64,
/// RFC 9111 Age, including time before the object entered this cache.
pub age_seconds: u64,
/// Total time held by RustProxy HOT/COLD; excludes any upstream CDN Age.
pub resident_age_seconds: u64,
pub remaining_ttl_seconds: u64,
}
#[derive(Debug, Clone, Serialize)]
+18 -15
View File
@@ -137,26 +137,29 @@ pub fn plan(
} else {
None
};
// Keep the freshness lifetime separate from validator/stale retention.
// `no-cache` may be stored with a zero freshness lifetime but must validate
// every reuse; it is retained by the bounded revalidation horizon.
let ttl = match declared_ttl {
Some(value) => value.min(settings.max_ttl_seconds),
None if requires_revalidation => 0,
None => return None,
};
let age = match headers.get("age") {
Some(v) => v.to_str().ok()?.parse::<u64>().ok()?,
None => 0,
};
// Keep sub-second precision internally and floor only when serializing the
// Age header. RFC 9111 adds response delay after correcting received age.
// RFC 9111 Section 4.2.3: the corrected initial age is the greater of
// the Date-derived apparent age and the received Age plus response delay.
// An upstream/CDN Age must be preserved on both transformed and normal HITs.
let apparent_age = now.duration_since(date).unwrap_or_default();
// RFC 9111 current_age: corrected_received_age is the greater of Date-based
// apparent age and the received Age value; response delay is then added to
// that result (not only to the Age branch).
let corrected_received_age = apparent_age.max(Duration::from_secs(age));
let initial_age = corrected_received_age.saturating_add(response_delay);
let corrected_age_value = Duration::from_secs(age).saturating_add(response_delay);
let initial_age = apparent_age.max(corrected_age_value);
// `ttl` is an absolute freshness lifetime (measured from the origin's
// Age clock), not the time since insertion into our HOT/COLD cache.
// Capping it directly to max_ttl_seconds makes an otherwise fresh 30-day
// upstream response with Age=18 days look expired when our own cap is 1 day.
// Limit the REMAINING freshness from admission, but preserve the Age clock.
// Origin freshness still wins: an origin response that has already expired
// must never become fresh here. `no-cache` continues to require validation.
let ttl = match declared_ttl {
Some(value) => value.min(initial_age.as_secs().saturating_add(settings.max_ttl_seconds)),
None if requires_revalidation => 0,
None => return None,
};
if !requires_revalidation
&& initial_age.as_secs()
>= retention_deadline(
+45
View File
@@ -89,6 +89,51 @@ async fn selective_purge_removes_matching_cold_object() {
shutdown.cancel();
}
#[tokio::test]
async fn aged_transformed_css_remains_fresh_when_demoted_to_cold() {
let dir = tempfile::tempdir().unwrap();
let settings = cold_settings();
let shutdown = CancellationToken::new();
let cache = Cache::start(11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone());
let ticket = cache.ticket_scoped(
11, "cold-test", "http", "cold.example", "origin@127.0.0.1:8080",
"/aged-theme.css", &HeaderMap::new(),
).unwrap();
let body = b"body { color: red; margin: 0px; }";
let mut origin = Response::new(Body::from(Bytes::from_static(body)));
origin.headers_mut().insert("cache-control", HeaderValue::from_static("public, max-age=2592000"));
origin.headers_mut().insert("age", HeaderValue::from_static("1553483"));
origin.headers_mut().insert("date", httpdate::fmt_http_date(SystemTime::now()).parse().unwrap());
origin.headers_mut().insert("content-type", HeaderValue::from_static("text/css"));
origin.headers_mut().insert("content-length", HeaderValue::from_str(&body.len().to_string()).unwrap());
origin.headers_mut().insert("etag", HeaderValue::from_static("\"origin\""));
let origin_plan = plan(StatusCode::OK, origin.headers(), &settings, Duration::ZERO).unwrap();
let transformed = crate::transform::apply(origin, "/aged-theme.css",
&crate::transform::Settings { enabled: true, css: true, ..Default::default() }, false
).await.unwrap();
let transformed_plan = plan(StatusCode::OK, transformed.headers(), &settings, Duration::ZERO).unwrap();
cache.wrap(transformed, ticket.clone(), origin_plan.restrict(transformed_plan), &settings)
.into_body().collect().await.unwrap();
assert_eq!(cache.view().entries, 1);
let _another = fill(&cache, &settings, "/another", "another-payload").await;
wait_for_cold(&cache, 1).await;
let row = cache.dashboard(Some("cold-test"), 0, 20).objects
.into_iter().find(|v| v.path == "/aged-theme.css").unwrap();
assert_eq!(row.tier, "cold");
assert!(row.transformed);
assert!(row.remaining_ttl_seconds >= 86_000);
shutdown.cancel();
drop(cache);
let shutdown = CancellationToken::new();
let cache = Cache::start(11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone());
let hit = cache.get_tiered(&ticket, &settings).await.expect("aged transformed COLD should HIT after restart");
assert!(hit.transformed);
assert!(hit.remaining_ttl >= 86_000);
let transformed_body = hit.response.into_body().collect().await.unwrap().to_bytes();
assert_ne!(transformed_body.as_ref(), &body[..]);
shutdown.cancel();
}
#[tokio::test]
async fn lru_demotion_persists_and_survives_restart() {
let dir = tempfile::tempdir().unwrap();
+107 -1
View File
@@ -606,7 +606,7 @@ mod tests {
..Default::default()
};
let body = br#"{ "a": 1 }"#;
let mut response = Response::new(Body::from(body.as_slice()));
let mut response = Response::new(Body::from(Bytes::from_static(body)));
response
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
@@ -1258,6 +1258,93 @@ mod tests {
.unwrap();
assert_eq!(fresh.initial_age.as_secs(), 0);
}
#[test]
fn aged_upstream_freshness_caps_remaining_not_total_age() {
let settings = settings();
let mut headers = headers("public, max-age=2592000");
headers.insert("date", httpdate::fmt_http_date(SystemTime::now()).parse().unwrap());
headers.insert("age", HeaderValue::from_static("1553483"));
headers.insert("etag", HeaderValue::from_static("\"origin-etag\""));
let original = plan(StatusCode::OK, &headers, &settings, Duration::ZERO).unwrap();
assert_eq!(original.initial_age.as_secs(), 1553483);
assert_eq!(original.remaining_ttl(), settings.max_ttl_seconds);
assert_eq!(original.ttl, 1553483 + settings.max_ttl_seconds);
// Local cap never overrides the remaining origin lifetime.
headers.insert("age", HeaderValue::from_static("2591990"));
let almost_expired = plan(StatusCode::OK, &headers, &settings, Duration::ZERO).unwrap();
assert_eq!(almost_expired.remaining_ttl(), 10);
headers.insert("age", HeaderValue::from_static("2592010"));
assert_eq!(
plan(StatusCode::OK, &headers, &settings, Duration::ZERO)
.unwrap().remaining_ttl(), 0
);
// No-cache and private/no-store restrictions must not be relaxed.
headers.insert("cache-control", HeaderValue::from_static("public, no-cache, max-age=2592000"));
assert!(plan(StatusCode::OK, &headers, &settings, Duration::ZERO)
.unwrap().requires_revalidation);
headers.insert("cache-control", HeaderValue::from_static("private, max-age=2592000"));
assert!(plan(StatusCode::OK, &headers, &settings, Duration::ZERO).is_none());
}
#[test]
fn age_uses_rfc9111_max_of_apparent_and_corrected_age() {
let s = settings();
let mut h = headers("public, max-age=3600");
h.insert("date", httpdate::fmt_http_date(SystemTime::now()
- Duration::from_secs(10)).parse().unwrap());
h.insert("age", HeaderValue::from_static("3"));
let p = plan(StatusCode::OK, &h, &s, Duration::from_secs(2)).unwrap();
assert!(p.initial_age < Duration::from_secs(12));
assert!(p.initial_age >= Duration::from_secs(10));
h.insert("age", HeaderValue::from_static("40"));
let p = plan(StatusCode::OK, &h, &s, Duration::from_secs(2)).unwrap();
assert_eq!(p.initial_age.as_secs(), 42);
}
#[tokio::test]
async fn aged_transformed_css_remains_hot_and_visible_in_dashboard() {
let cache = Cache::new(1);
let settings = settings();
let ticket = ticket(&cache, &HeaderMap::new());
let original_body = b"body { color: red; margin: 0px; }";
let mut upstream = Response::new(Body::from(Bytes::from_static(original_body)));
*upstream.headers_mut() = headers("public, max-age=2592000");
upstream.headers_mut().insert("date", httpdate::fmt_http_date(SystemTime::now()).parse().unwrap());
upstream.headers_mut().insert("age", HeaderValue::from_static("1553483"));
upstream.headers_mut().insert("content-type", HeaderValue::from_static("text/css"));
upstream.headers_mut().insert("content-length",
HeaderValue::from_str(&original_body.len().to_string()).unwrap());
upstream.headers_mut().insert("etag", HeaderValue::from_static("\"upstream\""));
let origin_plan = plan(StatusCode::OK, upstream.headers(), &settings, Duration::ZERO).unwrap();
let transformed = crate::transform::apply(
upstream, "/theme.css", &crate::transform::Settings {
enabled: true, css: true, ..Default::default()
}, false
).await.unwrap();
assert!(transformed.extensions().get::<crate::transform::Applied>().is_some());
assert_eq!(transformed.headers()["age"], "1553483");
let transformed_plan = plan(StatusCode::OK, transformed.headers(), &settings,
Duration::ZERO).unwrap();
let combined = origin_plan.restrict(transformed_plan);
assert!(combined.remaining_ttl() > 86_000);
let first = cache.wrap(transformed, ticket.clone(), combined, &settings)
.into_body().collect().await.unwrap().to_bytes();
let hit = cache.get(&ticket).expect("aged, fresh CSS should be cached after transform");
assert!(hit.transformed);
assert!(hit.remaining_ttl >= 86_000);
assert!(hit.response.headers()["age"].to_str().unwrap().parse::<u64>().unwrap() >= 1553483);
let second = hit.response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(first, second);
let dash = cache.dashboard(None, 0, 10);
assert_eq!(dash.matching_objects, 1);
assert!(dash.objects[0].transformed);
assert!(dash.objects[0].age_seconds >= 1_553_483);
assert!(dash.objects[0].resident_age_seconds < 10);
assert!(dash.objects[0].remaining_ttl_seconds >= 86_000);
assert_eq!(dash.objects[0].hits, 1);
}
#[tokio::test]
async fn purged_inflight_fill_cannot_repopulate() {
let cache = Cache::new(1);
@@ -1290,6 +1377,25 @@ mod tests {
.is_none());
}
#[tokio::test]
async fn hot_hit_updates_lru_without_replacing_the_entry_or_losing_hit_counter() {
let cache = Cache::new(1);
let settings = Settings { max_entries: 2, ..settings() };
let h = HeaderMap::new();
let a = cache.ticket(1, "http", "example.test", "app", "/a", &h).unwrap();
let b = cache.ticket(1, "http", "example.test", "app", "/b", &h).unwrap();
let c = cache.ticket(1, "http", "example.test", "app", "/c", &h).unwrap();
fill(&cache, a.clone(), "payload-a", &settings).await;
fill(&cache, b.clone(), "payload-b", &settings).await;
assert_eq!(cache.get(&a).unwrap().hits, 1);
assert_eq!(cache.get(&a).unwrap().hits, 2);
fill(&cache, c.clone(), "payload-c", &settings).await;
assert!(cache.get(&b).is_none(), "the least recently used object is evicted");
assert_eq!(cache.get(&a).unwrap().hits, 3, "HOT hits do not reset on LRU refresh");
assert_eq!(cache.get(&c).unwrap().hits, 1);
assert_eq!(cache.view().evictions, 1);
assert_eq!(cache.view().entries, 2);
}
#[tokio::test]
async fn hot_byte_budget_evicts_before_another_fill_grows() {
let cache = Cache::new(1);
let s = Settings {