# 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. `scripts/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.