Files
RustProxy/docs/V0.24.6_AUDIT.md
T
2026-10-07 22:22:54 +02:00

58 lines
5.7 KiB
Markdown

# RustProxy 0.24.6 memory and cache-preset audit
## Scope
0.24.6 persists cache-preset intent and hardens temporary/background memory use. The cache numeric limits remain the runtime source of truth; `memory_preset` and `cold_preset` are metadata stored in the same revisioned JSON configuration row in SQLite so the administration UI can restore the selected state after reload.
This audit is source/static in the packaging workspace because Rust/Cargo and heap profilers are unavailable here. It adds compiler/runtime tests to the source tree and an offline memory-contract gate, but sustained RSS/heap acceptance must still run on a compiler-enabled release host.
## Cache preset persistence
| Area | Contract |
|---|---|
| Cache model | `Settings.memory_preset` accepts conservative/recommended/performance; `Settings.cold_preset` accepts recommended; old configurations deserialize both as `None`. |
| SQLite | `Store::replace` serializes the complete cache settings into `configuration.document`; the database reopen test now verifies both preset values survive close/reopen. |
| Cache identity | Preset metadata is excluded from `cache_revision()`, so changing only the selected label cannot purge HOT/COLD data. Effective numeric/policy changes still rotate cache identity normally. |
| UI | Settings restores the selected button/badge from configuration. Manual edits clear the affected preset metadata. |
| Recommended COLD | Enables COLD, idle/LRU/shutdown demotion, disables age demotion, sets 300 s idle, 3600 s max-hot, 60 s GC and 256 max-delete, and chooses a queue around half transient RAM. Disk path, disk bytes and entry/object limits are preserved. |
## Transformation memory review
| Path | 0.24.6 behavior |
|---|---|
| Concurrent transforms | A process-wide 128 MiB semaphore budget accounts for collected input plus expected decoded/output buffers. Transform is fail-open with `memory_pressure` when the budget is busy. |
| Response aggregation | `http_body_util::Limited` enforces `max_body_bytes` even when upstream `Content-Length` is false; over-limit aggregation becomes a body error instead of allocating without bound. |
| Decompression | Fixed 16 KiB reads and exact bounded reserve checks reject expansion beyond `max_body_bytes`. |
| JSON | `IgnoredAny` validates syntax without materializing `serde_json::Value`; whitespace compaction is one pass and preserves number lexemes/key order. |
| HTML | Raw element detection compares tag slices case-insensitively instead of allocating/lowercasing a tag name for every element. |
| CSS | Tracks the last output character instead of repeatedly scanning the full output string. |
| Large-buffer release | Existing glibc `malloc_trim(0)` throttling remains after successful large transforms. |
## Background-worker memory/task review
| Facility | Bound/ownership reviewed |
|---|---|
| Cache COLD writer | Existing object-count and byte budgets remain; delete fallback is limited to 2 detached tasks and otherwise applies bounded synchronous backpressure. |
| COLD cleanup | Manual/automatic cleanup share a single-flight semaphore. Candidate selection uses a bounded heap sized to the requested deletion limit; delete path metadata is flushed per 32-entry batch. |
| Stale-while-revalidate | Distinct background revalidations are capped at 8, limiting retained cached `Bytes` when objects are concurrently evicted/replaced. |
| Proactive refresh | Sleeping scheduled-key tasks are capped at 64..256 depending on configured refresh concurrency; actual refreshes keep the existing `proactive_refresh_max_inflight` bound. |
| Logging | History and broadcast buffers are fixed at 4096; sink delivery uses a bounded channel and `try_send`. |
| Diagnostics | One cancellation-aware sampler loop; each blocking sample is awaited before the next iteration. |
| DNS | Shared semaphore of 16 blocking resolver workers plus timeout; a timed-out OS resolver still owns its permit until the worker exits. |
| Backend health | One supervisor with a `JoinSet`, maximum 16 probes, at most one claimed probe per member, abort/drain on shutdown. |
| TCP/UDP streams | Per-stream connection/session semaphore, bounded UDP channel, `JoinSet` ownership and cancellation. |
| HTTP listeners | Accepted connections are gated by `max_connections`; connection tasks are owned by the listener `JoinSet`. App-owned tunnel/background tasks use `TaskTracker`. |
| Configuration writes | Validation/publication work is bounded by 8 update slots and serialized publication lock. |
| ACME | Issuance/renewal is single-flight through a semaphore; the scheduler is one cancellation-aware loop. |
| Docker discovery / update checker | One cancellation-aware periodic loop each; no per-tick detached task accumulation. |
| Admin live streams | Viewer permits and bounded broadcast/snapshot semantics prevent per-client unbounded queues. |
| Tunnel registry | Stores weak references and removes dead sessions on drop/list cleanup. |
## Added regression coverage
Rust tests cover preset serialization/database reopen, preset-only cache identity stability, transform memory-budget math, bounded decompression, false Content-Length aggregation, proactive pending-task limits, stale revalidation limits and single-flight COLD cleanup. `tools/checks/memory_contracts.py` independently verifies the source-level bounds above when Cargo is unavailable and is part of `./dev.sh test`.
## Release-host acceptance gate
Run `./dev.sh fmt`, `./dev.sh check` and `./dev.sh test`, then exercise representative transform/cache traffic while recording RSS/heap. Acceptance target: RSS reaches a plateau after warm-up; transform concurrency never exceeds the 128 MiB temporary budget estimate; COLD cleanup and revalidation counts remain bounded; no monotonically growing task/map/queue is observed after traffic stops.