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

5.7 KiB

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.