Two problems on the exit paths out of the runStream loop, both raised in review.
paused_image and stopped_image were raw pointers freed only by a later iteration
observing that the state had changed. Any break leaves the function with one
still allocated: the stopped-state ttl break added by this branch, and the ttl
and frames_to_send breaks at the bottom of the loop that predate it. Hold both
in unique_ptr so the exit path stops mattering.
More seriously, those breaks leave zm_terminate false. StreamBase::checkCommandQueue
loops on !zm_terminate around a recvfrom with a one second SO_RCVTIMEO, so it
returns only once that flag is set, and runStream joins it unconditionally when
connkey is set. A stream that ended on its ttl or its frame count therefore sat
in join() rather than exiting, which is another way for a zms to outlive its
client. Set the flag on all three.
The two sendFrame failure breaks and the feof/ferror breaks already did this,
which is what the pattern should have been throughout.
Verified by reading: checkCommandQueue's only loop condition is the flag. Not
reproduced end to end, because no zmc is running on this machine and a stream
against a monitor with no capture takes the not-initialised path instead, which
already sets the flag correctly on its own timeout.
zms builds clean with no new warnings. C++ suite 133/133 on this branch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkQwahn9pi1y4wJe9BTxjM