fix: unlink the zms command socket on exit - #5034
Conversation
closeComms() left /var/lib/zoneminder/sock/zms-NNNNNNs.sock behind, and the only thing that ever removed it was the unlink() before bind() in a later zms that happened to draw the same connkey. Socket files accumulated indefinitely. web/ajax/stream.php uses file_exists() on that path to decide whether zms is listening, and waits up to a second for it to appear before giving up. A file left by an exited zms defeats that wait: the check passes immediately, the sendto() gets ECONNREFUSED, and the command is reported as failed even though the new zms was about to bind. genConnKey() draws from six digits, so a page cycling monitors every five seconds reuses a key well within an hour. Unlink while we still hold the flock. A second zms with the same connkey blocks on flock(LOCK_EX) in openComms() before it unlinks and binds, so it cannot have created its own socket yet and we can't delete a file belonging to it. The lock file itself is still left alone, since another zms may be waiting on it. This is best effort: zms killed by a signal still leaves the socket behind. In that case the process is usually still running, so the file being there is not wrong. Also reset lock_fd after closing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
openComms() logs an error but carries on to bind() when it cannot open or flock() the .lock file, so a zms can be serving without holding the lock. In that state the unlink in closeComms() could remove a socket belonging to the zms that does hold the lock, leaving it unreachable. Guard the unlink on lock_fd >= 0. Leaking the socket file when we never held the lock is no worse than the behaviour before the unlink was added. Expand the comment to spell out that this path is the command socket rather than the .lock file, that our successor unlinks it itself on the way to bind(), and why the unlink has to precede releasing the lock. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Raised in review: we deliberately don't delete these files, because a second zms may be waiting on the lock and would then be unable to access it. Worth separating the three files involved, because the rule is guarding a different one than this patch touches:
The waiting-on-the-lock concern is about file 1. This patch removes file 2 and leaves file 1 alone. A second zms blocked in The part that settles it is what the waiter does once it wins the lock, in // Unlink before bind, in case it already exists
unlink(loc_sock_path);It deletes its predecessor's socket file itself. It never reads it or connects to it. So removing it in Two supporting mechanics:
The concern did surface a real bug, though, pushed as e75a8b9.
Ordering is load-bearing for the same reason and was already correct: unlinking after releasing the lock could delete a socket the successor had just bound. The comment now spells all of this out. Testing: |
Split out of the investigation in #5029. Independent of #5033 and of the
mode=singlequestion in #5029.closeComms()left$PATH_SOCKS/zms-NNNNNNs.sockbehind on exit. The only thing that ever removed it was theunlink()beforebind()in a later zms that happened to draw the same connkey, so these files accumulate indefinitely.That matters because
web/ajax/stream.phpusesfile_exists()on exactly that path to decide whether zms is listening, and waits for it to appear before giving up:A file left by an exited zms defeats that wait: the check passes immediately, the subsequent
socket_sendto()gets ECONNREFUSED, and the command is reported as failed even though a new zms was about to bind.genConnKey()draws from six digits, and a page cycling monitors every five seconds burns ~720 connkeys an hour, so a reuse is likely well within an hour of viewing.This is the mechanism behind the "Socket ... does not exist" / "Timed out waiting for msg" errors in #5029, which in turn trigger the connkey regeneration that orphans the running zms.
Why unlinking is safe here. The existing comment says we can't delete the files because another zms may have opened them and be waiting on the lock. That holds for the
.lockfile, which this PR still leaves alone. It doesn't hold for the command socket: a second zms with the same connkey blocks onflock(LOCK_EX)inopenComms()before it unlinks and binds, so while we still hold the lock it cannot yet have created its own socket, and we cannot be deleting a file that belongs to it.Limitation: best effort. A zms killed by a signal still leaves the socket behind. In that case the process is usually still running, so the file being present isn't wrong.
Also resets
lock_fdto -1 after closing it.Testing: builds clean (
cmake --build . --target zms, Debug). Not verified against a live install — the useful check is watching$PATH_SOCKSstop accumulating*s.sockfiles over a long montage session.