fix: release large muxer write buffers to the OS - #330
Conversation
|
Hey, thanks for the PR The write path really does allocate a fresh copy per call, and at realistic fragment lengths those writes really are large and variably sized, so that part is accurate. I measured it here and got p50 17.7 KiB / p90 78 KiB with 4s fragments, which lines up with what you describe. Where I get stuck is the conclusion. I tried to reproduce the unbounded growth before merging and I can't. Setup: Debian bookworm, glibc 2.36, node-av 6.2.0-beta.17, a single long-lived Default glibc: 284 MiB at t=60, peak 308, and completely flat from t=160 to t=300. With I also ran a control with no node-av at all, just Two things about the patch itself, independent of the above. The I'm not closing this. What would move it forward for me is a longer run, a few hours rather than ten minutes, showing that the curve does not flatten out, plus your exact glibc version and platform, and the |
63467e7 to
76016bb
Compare
|
Thanks for the detailed reproduction and review. Your three patch-level points are correct, and I updated the branch in
I also rebased onto current The affected production environment and actual FMP4 settings are:
I agree that the earlier 10-minute isolated run alone was not enough to extrapolate indefinitely. The production history now gives a longer window, though: after a restart on August 19 at 18:36 CEST, Proxmox RRD shows the container below 1 GiB around 19:30, ~1.6 GiB at 20:00, ~5.2 GiB at 20:30, ~9.4 GiB at 21:00, and at the 10 GiB cgroup limit by 21:30. It then stays pinned there by reclaim. At 00:16 CEST the HomeKit process itself had 9,360,356 KiB RSS, of which 9,360,352 KiB was anonymous/private dirty, with zero swap. The cgroup had >53 million So I agree with avoiding a blanket conclusion from the short synthetic curve, but the real four-stream remux workload does not plateau near 300 MiB. The platform difference may matter as well: production is glibc 2.39, not 2.36. For allocation isolation, the earlier test was a single I also revised #328 and this PR description to separate the multi-hour production non-plateau evidence from the shorter isolated allocator A/B evidence, and removed the claim that the ten-minute run by itself proved an indefinite linear rate. |
|
Additional verification for the revised head I reconstructed the repository's Linux x64 CI build locally in an isolated worktree using the project's Jellyfin FFmpeg build inputs and the Environment:
Results:
This closes the previous verification gap caused by the incomplete restored FFmpeg build tree. The GitHub fork workflow still requires maintainer approval before it can run. |
Summary
Sustained fMP4 muxing with large, variably sized writes can create severe process-heap retention under real long-lived camera workloads. This pull request moves large transient
IOContext::WritePacket()callback buffers out of the regular process heap while preserving their copy and lifetime semantics.Fixes #328
Allocation path
IOContext::WritePacket()currently copies every FFmpeg write throughNapi::Buffer<uint8_t>::Copy(). The affected production workload uses four permanent HKSV prebuffers, 4-second fragments, the default 2 MiB AVIO buffer, and high-resolution H.264 remux input. The resulting large, variably sized writes repeatedly churn the Node/libc heap.Changes
mmapon POSIX,VirtualAllocon Windows).finalize_hintinstead of storing a header in the mapping.Napi::MemoryManagement::AdjustExternalMemory()and subtract it in the finalizer.FFmpeg data is still copied before the callback returns, so the patch does not introduce a zero-copy lifetime hazard.
The previously included
FMP4Stream.emitBoxGroup()view optimization has been removed from this PR. It is independent and changes observable backing-buffer/byteOffsetsemantics.Production evidence
Affected deployment:
fragDuration: 4_000_000bufferSize: 2 * 1024 * 1024MALLOC_*tunablesA run started on August 19, 2026 at 18:36 CEST. Proxmox RRD data shows the container below 1 GiB around 19:30, approximately 1.6 GiB at 20:00, 5.2 GiB at 20:30, 9.4 GiB at 21:00, and at its 10 GiB cgroup limit by 21:30. At 00:16 CEST the HomeKit process alone held 9,360,352 KiB of anonymous/private-dirty RSS. The cgroup had more than 53 million
memory.highevents and no OOM kill. A preceding run had reached the same limit before restart.This does not show the 300 MiB warm-up plateau seen in the single-stream Debian/glibc-2.36 reproduction. Short isolated tests against the same real camera source, without HomeKit/HAP queues, also showed lower post-GC growth with
MALLOC_MMAP_THRESHOLD_=65536or a low trim threshold.Verification
The revised head
76016bbhas been verified with:npm run build:testsnpm run build:tscnpm run lintnpm run lint:gypgit diff --checkprebuildify --napi --strip --arch=x64 --tag-libcThe GitHub workflow for this fork PR still requires maintainer approval before its jobs can start.