Fix/unbounded tosend buffer - #52
Open
John Walicki (johnwalicki) wants to merge 5 commits into
Open
Conversation
Signed-off-by: John Walicki <johnwalicki@gmail.com>
Signed-off-by: John Walicki <johnwalicki@gmail.com>
Signed-off-by: John Walicki <johnwalicki@gmail.com>
Signed-off-by: John Walicki <johnwalicki@gmail.com>
Signed-off-by: John Walicki <johnwalicki@gmail.com>
Author
|
I did a hot reload of this patched module onto the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix unbounded ToSend buffer that crashed the module out of memory
Fixes the
viam_filtered-cameracrash onbijan-test-windows-2(module 2.1.0, windows/amd64). The module exited withexit_code=2after its send buffer grew to 1024 entries, then restarted and immediately began growing again at 1/sec.Root cause
Two independent defects had to line up:
captureFromandcaptureTillare the zerotime.Time. A source camera that returns an unpopulatedResponseMetadatagives us a zeroCapturedAt. The window check ended in|| now.Equal(ib.captureFrom), sozero.Equal(zero)was true and every background frame was filed as "inside a capture window" that had never been opened.toSendhad no cap.ringBufferis bounded bymaxImages;toSendwas only drained when data management asked for images, and past the warning threshold it just logged. Nothing stopped it growing.The log confirms the sequence. After the restart at
11:26:15.97, the first warning at11:28:16.98reports size 121 — 121.01 seconds later, i.e. buffer size == seconds since startup, to the frame, with zero drainage. And onlyimage_buffer.go:348(StoreImages) ever warns;MarkShouldSend's identical warning at:128never appears, so no trigger ever fired and the window was never legitimately open.The goroutine dump carries
fp=/sp=/pc=on every frame and includes runtime-internal frames — aGOTRACEBACK=systemdump, which the Go runtime forces only for a runtime throw, not an ordinary panic.What changed
rdkv0.124.0-rc0 → v1.1.0 (andgo.viam.com/utils→ v0.8.1), via themake updaterecipe. No source changes were required.withinCaptureWindowLocked, which rejects an uninitialized window (captureTill.IsZero()) and an unstamped frame (now.IsZero()) before the inclusiveEqualcomparisons run. Unstamped frames land in the bounded ring buffer instead.toSendis now hard-capped atmax(maxImages * 5, 100)— 300 for the config that crashed, above the existing warning threshold of 120 so the warning still fires first. Enforced on both append paths (StoreImagesandMarkShouldSend), dropping oldest and copying into a fresh backing array so the shed images are actually collectable rather than pinned by a re-slice.CapturedAtis filled in with wall-clock time bynormalizeCapturedAt, applied incaptureImageInBackgroundand in the data-management path ofimages(). The latter matters becauseMarkShouldSend(meta.CapturedAt)would otherwise anchor the whole capture window to year 1. Warns once, naming the offending camera, rather than once per frame.toSendmeans it now sits pinned at the cap under a slow consumer, so the old per-frame warning would have flooded the log forever (it was already 900+ lines in the crash log). Each episode now logs one WARN at the threshold and one ERROR at the cap, re-arming once the buffer recovers.camera.Camerahas noImagemethod in rdk v1.1.0, neither model defines one, and bothimages()callers passfalse, sosingleImageModecould never be true. DeletedPopFirstToSend(whosetoSend[1:]re-slice retained its backing array) and the parameter threaded throughgetBufferedImages()/images()in both models.No config attributes changed; existing configs need no edits.
Commits
e8ed509da211bb08a1b6ef40d0c09288c84Commits 2 and 3 are the two independent halves of the crash — either alone would have prevented it. Commit 4 fixes the upstream input that triggered it. The ordering is deliberate: containment lands first, so the tree is never in a state where the OOM is reachable.
Files
image_buffer/image_buffer.go— window predicate,toSendcap, edge-triggered notices,PopFirstToSendremovedcam.go—normalizeCapturedAt,singleImageModeremovedconditional_camera/conditional_cam.go—singleImageModeremoved (mechanical; forced by deletingPopFirstToSend)image_buffer/image_buffer_test.go,cam_test.go— 5 new regression testsREADME.md— documents that both buffers are bounded and what the two log messages mean; drops theImage()bullet for the removed pathgo.mod,go.sum— rdk bumpTesting
TestBackgroundCaptureWithUnstampedCamerareplays the actual failure: a camera returning emptyResponseMetadata, 1024 background captures (17 min at 1Hz), assertingtoSendstays at 0. Against the old code that test produces a 1024-entry buffer.Not addressed
conditionalCamerastill passes rawmeta.CapturedAtto the shared buffer. It cannot reproduce this OOM — no background worker, and its only write path is the boundedAddToRingBuffer— but with an unstamped source camera it will silently capture nothing. Left for a separate change.