Skip to content

fix: close and disconnect zmc's monitors when stopped during startup - #5221

Merged
connortechnology merged 1 commit into
ZoneMinder:masterfrom
nabbi:fix/b65-zmc-stop-during-startup
Oct 12, 2026
Merged

connortechnology merged 1 commit into
ZoneMinder:masterfrom
nabbi:fix/b65-zmc-stop-during-startup

Conversation

@nabbi

@nabbi nabbi commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Automated PR, tested in a zm-holodeck sandbox. An AI agent (Claude Opus 5.5) reproduced
the bug on a disposable ZoneMinder built from upstream master c3d2e542b with
simulated cameras, wrote this fix and ran the checks below on the same lab. Minimal
human review before opening: please review it as you would any contributor's PR.

TL;DR

  • Fixes: zmc stopped while it is still starting leaves its monitor threads running past logger and database teardown #5220: #: a SIGTERM during zmc startup skips Close()/disconnect() and leaves the monitor threads running past logger and database teardown.
  • Change: drop the if (zm_terminate) break; after the startup loop; with zm_terminate set the capture loop doesn't run, so the existing Close()/disconnect() loop runs and the outer loop ends as on a normal stop; 1 file, +3/-1
  • Risk: Close()/disconnect() now also run on monitors that never connected in this pass; both already handle that (reconnects run them on every monitor); stop times unchanged
  • Tested: ThreadSanitizer, 20 stops during startup: 56 thread-leak reports and 13 logger exit races each before, none after; checks covering the diff green
Details: reproduction, change, checks

Before

Upstream master c3d2e542b, ThreadSanitizer build, 640x360 cameras; zmc -m 4 (ONVIF monitor)
started and stopped 0.5-3 s later, 20 times; reports from those processes:

  1     56  thread leak ... in std::thread::_M_start_thread(...)  [zmc]
  2     13  data race ../../../../src/libsanitizer/tsan/tsan_new_delete.cpp:150 in operator delete(void*, unsigned long)  [zmc]
  3     13  data race /src/src/zm_logger.cpp:350 in Logger::databaseLevel(int)  [zmc]
  4     13  data race /src/src/zm_logger.cpp:589 in logTerm()  [zmc]
  5     13  data race /src/src/zm_logger.cpp:80 in Logger::Logger()  [zmc]

Change

  • src/zmc.cpp: remove the if (zm_terminate) break; after the startup loop (:328), with a
    comment saying why. The breaks inside the startup loop (:276, :312) only leave the for
    and now reach the Close()/disconnect() loop too. For a monitor that never got that far,
    Close() → Pause() finds no decoder, analysis thread, camera or event, and disconnect()
    returns early on mem_ptr == nullptr after clearing the zones (the same calls a reconnect
    makes on every monitor).
  • Test: no unit test: the path is zmc's main(). Covered by the TSan probe above and holodeck's
    e2e/signals.py startup-stop scenario.

Behaviour changes

Change Who sees it Intended / collateral Alternatives looked into Chosen, why Tested by
A zmc stopped during startup closes and frees its monitors before exiting zmdc, logs intended Zone holding a non-owning pointer: frees the Monitor via ~Monitor → Close(), but a larger change touching every Zone user, and the loop is skipped anyway; Close()/disconnect() in a second loop after the outer loop: runs them twice on a normal stop reach the existing loop TSan probe; signals startup-stop PASS
A SIGHUP that arrived together with the SIGTERM during startup now also runs the reload block before exiting nobody (exit follows) collateral, same as a stop during capture today guard the reload with !zm_terminate: a further change for no visible effect unchanged
Stop time during startup zmdc no visible change startup-stop delay/stop times below

Side fixes in this PR: none.

After

Branch fix/b65-zmc-stop-during-startup at 856cb250e (base c3d2e542b), tested in a merge with
two other fixes from the same lab session (combo = master + this +
fix/b55-usr-level-signal-safe + fix/b77-monitorlink-token-leak; no shared files):

"head": "69fa500a6683a16c937ae8be4d249a7eb52ba861", "branch": "combo", "dirty": false (ThreadSanitizer)
PASS  startup-stop: zmc -m 4 (onvif-1) started and stopped 20x (delay/stop time 0.8s/7.2s 2.6s/9.9s 2.4s/0.9s 1.1s/1.2s 1.7s/0.6s 1.6s/0.6s 2.1s/1.2s 2.5s/0.9s 0.7s/0.6s 0.6s/0.6s 2.6s/0.6s 1.6s/0.6s 2.4s/0.9s 0.5s/0.6s 1.6s/0.6s 2.3s/1.2s 1.1s/0.6s 2.9s/10.2s 2.8s/9.9s 0.6s/0.6s) [116s]
reports from those processes: no thread leak, no race on logTerm/Logger; only the known capture/decode races
(zm_monitor.cpp:3096, :3687, :2366, zm_packet.cpp:397/404, zm_monitor.h:1223), present on master too
Check Result
holodeck checks for the diff (check-changed.py: unit-tests, script-tests, smoke, resilience, events, dblog --quick, signals --quick; plain build of combo) unit-tests ok (14725 assertions in 251 test cases), script-tests PASS=31, smoke PASS=11 XFAIL=1 (stock zmonvif-probe.pl discovery, known), resilience PASS=5, events PASS=10, dblog --quick PASS=1 XFAIL=2 (known on master, #5197/#5208), signals --quick PASS=4 (hup-default, usr, usr-threads, startup-stop)
pyzmNg / ha-zoneminder / zmNinjaNg not run: no API, web or stream change
contract baselines (master) not run: no PHP change

Not run: the deep set. Backport to release-1.38: same code, the commit cherry-picks cleanly (not built).

Tested as 8cf52ca5c; 856cb250e is the same tree with fixes #5220 added to the message.

fixes #5220

🤖 Generated with Claude Code

A SIGTERM while zmc is still connecting or priming its monitors broke
out of the outer loop at the "if (zm_terminate) break;" after the
startup loop, skipping the Close()/disconnect() loop. Monitors that had
connected kept their decoder, analysis, poller and ONVIF threads running
through dbQueue.stop(), zmDbClose() and logTerm() at the end of main,
and were never destroyed: each Zone holds a shared_ptr to its Monitor
and only disconnect() clears the zones.

Drop that break. With zm_terminate set the capture loop doesn't run, so
the Close()/disconnect() loop runs next and the outer loop ends as it
does on a normal stop. Close() and disconnect() already run on monitors
that never connected or are already disconnected (reconnects do that).

fixes ZoneMinder#5220

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@connortechnology
connortechnology merged commit 618cec4 into ZoneMinder:master Oct 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zmc stopped while it is still starting leaves its monitor threads running past logger and database teardown

2 participants