mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-08-03 08:46:44 -04:00
Recovery from a transient stream error could spiral into a permanent loop that spawned and abandoned zms processes and produced endless "giving up" errors. Three faults are fixed: - recover() dropped the dead connection only on the retry path, so the give-up branch returned with the status-poll timer and rAF loop still running. Each poll reply was another Error, each Error re-entered recover(), which gave up again forever. Tear down before the give-up check so giving up leaves nothing running. - recover() had no guard against being called while a retry was already scheduled. Errors arriving during the backoff wait inflated the attempt counter until it gave up without ever retrying once. Ignore recover() while a recoveryTimer is pending. - The recovery timer and start() both reused the existing <img>, and a consumer polling for a live stream (montagereview's outputUpdate) would call start() during the backoff window. The delayed recovery then overwrote the freshly started stream's src, aborting a healthy stream and orphaning its zms, which fired onerror straight back into recover(). start() now tears down any existing connection first, and the recovery timer bails if the stream was already restarted. Factor the duplicated teardown in stop()/recover()/switchEvent() into a teardown() helper. switchEvent() now restarts via start() directly instead of a timer, closing the same started=false race. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>