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. |
|
Releasing Beta 19 https://github.com/seydx/node-av/actions/runs/32320409935 can you test it pls once its released |
|
Beta 19 is now running in the affected production environment. Deployment details:
I replaced only Before the controlled restart, the beta.18 HomeKit process had been running for about 7h45m and held:
The beta.19 HomeKit process started at 06:48:36 UTC. Initial observations under all four active prebuffers:
Since that warm-up step, RSS has fluctuated in a narrow roughly 347-350 MiB range rather than rising continuously. Threads stayed at 27 and FDs stayed essentially stable. The container is healthy and there are no node-av/HomeKit errors. This is only an initial result, not yet evidence of a long-term plateau. I am continuing the same workload and collecting longer-run samples before drawing a conclusion about the two beta.19 packet-release fixes versus the IOContext allocation change in this PR. |
|
any news? |
|
Long-run beta.19 result from the affected production workload: Environment / versions
The current HomeKit process started at 2026-08-20 07:13:10 UTC and has now run for more than 13h34m under the same real four-camera workload. Its current and lifetime values are:
For comparison, beta.18 had reached 2,230,480 KiB RSS and 2,153,504 KiB anonymous RSS after only about 7h45m. Even beta.19's lifetime maximum RSS is about 80.0% below that beta.18 observation; its current anonymous RSS is about 84.7% below beta.18. The Docker container remains running and healthy with:
There is one separate caveat. The first beta.19 HomeKit process, started at 06:48:36 UTC, exited unexpectedly at 07:13:05 UTC after about 24 minutes and was restarted by the camera.ui plugin supervisor five seconds later. Its last valid sample was only 351,572 KiB RSS / 275,164 KiB anonymous RSS. There was no kernel/cgroup OOM, no Docker restart, and no core dump; camera.ui logged One measurement note for transparency: the CSV sampler's PID selection became invalid after its first two samples because My current conclusion is therefore deliberately scoped: beta.19 has so far eliminated the severe multi-hour RSS growth in this real production workload. The two packet-lifetime fixes included in beta.19 appear sufficient for the original memory-exhaustion symptom here, despite beta.19 not containing the IOContext allocation change from this PR. On this evidence, that additional change may no longer be necessary for this issue. The single early HomeKit-process exit remains worth tracking separately if it recurs. |
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.