mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-14 14:19:45 -04:00
getStreamCmdResponse() responded to every ajax/stream.php failure the same way: mint a fresh connkey and reload the img src. ajaxError() returns HTTP 200 with result=Error, so these arrive in jQuery's done() rather than fail(), and all twelve error paths in stream.php took that branch. Only one of them means zms is gone. For the rest the process is still running and streaming, and replacing the connkey makes it unaddressable: CMD_STOP, CMD_QUIT and mode=single all then go to the new key, so nothing can reach the old process and only SIGPIPE can stop it, which we know is unreliable. That is why the reports of lingering zms after switching monitors were unaffected by changes to what the stop path sends. The timeout path made this routine rather than rare. On select() expiry ajaxError is commented out, so the script carries on to socket_recvfrom() on a now non-blocking socket. That returns false, and false == 0 under switch's loose comparison, so a merely slow zms was reported as 'No data to read from socket' and torn down. stream.php now classifies each failure as no_socket, timeout, transient or invalid, and sends it as 'reason'. The client restarts the stream only for no_socket. A missing reason is still treated as fatal, so a php that predates this keeps the old behaviour. Before replacing the connkey the client now sends CMD_QUIT to the old one, so the process we are about to lose track of is asked to exit. That is deliberately not routed through streamCommand(): it must name its target explicitly, since this.connKey is about to change, and its response must not feed back into getStreamCmdResponse(), or a QUIT that also failed would re-enter the error path and loop. ajaxError() takes the classification as a third argument, named $reason because $code is already the HTTP status, and only includes it when set, so the other 131 callers are unaffected. Tests: tests/js covers the fatal/non-fatal decision including the no-reason fallback, tests/php pins the classification mapping and the switch(false) semantics the timeout branch depends on. Both verified to fail when the behaviour is reverted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
64 lines
2.0 KiB
JavaScript
64 lines
2.0 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
const ZM = require(path.join(__dirname, '../../web/js/MonitorStream.js'));
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(' ok ' + name);
|
|
passed++;
|
|
} catch (e) {
|
|
console.error(' FAIL ' + name);
|
|
console.error(' ' + e.message);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
console.log('streamErrorIsFatal');
|
|
|
|
// Only a missing zms should tear the stream down. Restarting replaces the
|
|
// connkey, which makes any zms still running unreachable, so treating a
|
|
// recoverable failure as fatal is what orphaned the process.
|
|
test('no_socket -> fatal, zms really is gone', () => {
|
|
assert.strictEqual(ZM.streamErrorIsFatal('no_socket'), true);
|
|
});
|
|
|
|
test('timeout -> not fatal, zms is most likely alive and busy', () => {
|
|
assert.strictEqual(ZM.streamErrorIsFatal('timeout'), false);
|
|
});
|
|
|
|
test('transient -> not fatal, the failure was local to php', () => {
|
|
assert.strictEqual(ZM.streamErrorIsFatal('transient'), false);
|
|
});
|
|
|
|
test('invalid -> not fatal, restarting cannot fix a bad request', () => {
|
|
assert.strictEqual(ZM.streamErrorIsFatal('invalid'), false);
|
|
});
|
|
|
|
// A php that predates the reason field sends no reason at all. Falling back to
|
|
// the old always-restart behaviour keeps a mixed-version install working.
|
|
test('missing reason -> fatal, preserves pre-reason behaviour', () => {
|
|
assert.strictEqual(ZM.streamErrorIsFatal(undefined), true);
|
|
});
|
|
|
|
test('null reason -> fatal', () => {
|
|
assert.strictEqual(ZM.streamErrorIsFatal(null), true);
|
|
});
|
|
|
|
test('empty reason -> fatal', () => {
|
|
assert.strictEqual(ZM.streamErrorIsFatal(''), true);
|
|
});
|
|
|
|
// An unknown reason from a newer php should not be silently treated as fatal:
|
|
// the conservative choice is to leave the stream alone and retry.
|
|
test('unrecognised reason -> not fatal', () => {
|
|
assert.strictEqual(ZM.streamErrorIsFatal('something_new'), false);
|
|
});
|
|
|
|
console.log('\n' + passed + ' passed, ' + failed + ' failed');
|
|
process.exit(failed ? 1 : 0);
|