INSERT INTO Event_Summaries_New SELECT * FROM VIEW_Event_Summaries took
shared next-key locks on the source Events table (required for STATEMENT
binlog correctness under the default REPEATABLE READ). Concurrent DELETE
FROM Events deadlocked against those S locks.
Switch the procedure to READ COMMITTED so the source-side SELECT runs as
a consistent snapshot read with no row locks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 1.37.78 update slot is already taken on master by the User Roles
feature, so move the views migration to the next free slot, 1.39.5.
CREATE INDEX IF NOT EXISTS / DROP INDEX IF EXISTS in views.sql are
MariaDB-only and fail to parse on MySQL. Replace both with the
INFORMATION_SCHEMA.STATISTICS conditional pattern used throughout the
ZoneMinder update scripts.
Also tighten the migration script to drop the SWR procedure, scheduled
event, and any prior view-named objects before sourcing views.sql, so
re-running on a partially-migrated database is safe.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the Event_Summaries view with a physical snapshot table
refreshed via Stale-While-Revalidate (SWR) pattern. A stored
procedure (Refresh_Summaries_SWR) uses GET_LOCK for non-blocking
concurrency and atomic table rename for zero-downtime refresh.
Database changes (db/views.sql):
- Rename Event_Summaries view to VIEW_Event_Summaries (source view)
- Add Event_Summaries snapshot table and Event_Summaries_Metadata table
- Add Refresh_Summaries_SWR stored procedure with GET_LOCK and
atomic rename pattern to prevent thundering herd
- Add MySQL EVENT for background refresh every 600 seconds
PHP call sites (web/):
- Add ensureSummariesFresh() helper in database.php with static
per-request dedup and 60s staleness check
- Call ensureSummariesFresh() before Event_Summaries queries in
console.php, _monitor_filters.php, and Monitor.php
- Add beforeFind() hook in CakePHP Event_Summary model
Perl call sites (scripts/):
- Add ensureSummariesFresh() sub in Event_Summary.pm with
per-process 60s rate-limiting
- Call ensureSummariesFresh() in Monitor.pm Event_Summary accessor
Upgrade path (db/zm_update-1.37.78.sql.in):
- Drop any prior Event_Summaries view or table before recreating
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove denormalized event summary tables and their associated triggers,
replacing them with views that query the Events table directly. This
eliminates trigger maintenance overhead and periodic reconciliation in
zmaudit/zmstats, since the views compute stats on the fly.
- Remove trigger definitions for event summary table maintenance
- Remove event summary table inserts from zm_event.cpp
- Remove event count reconciliation queries from zmaudit.pl
- Remove DELETE-on-views calls from zmstats.pl (views filter by date inherently)
- Remove Event_Summaries DELETE from Monitor.php (can't delete from a view)
- Add db/views.sql with view definitions and covering index
- Add upgrade script zm_update-1.37.78.sql.in (drop triggers, drop tables, create views)
- Update zm_create.sql.in to use views instead of tables for fresh installs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Bug 376 (2006) closed all FDs starting from 0 when daemonizing. This
caused FD reuse problems: libx264 writing to a reused stderr FD led to
memory corruption (fixed in child spawn by 66f11435b, but not in the
parent's run()). It also meant children inherited closed FDs 0-2, so
any Perl die/warn output was silently lost, making daemon crashes
impossible to diagnose.
Redirect 0-2 to /dev/null (standard daemon practice) and close only
FDs 3+ for inherited sockets/DB connections. Children now inherit valid
FDs that won't crash or corrupt on write.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Warn() is a natural shorthand that's easy to reach for. Its absence
caused a silent crash in zmfilter when Warn() resolved to Perl's
built-in warn(), which wrote to a closed stderr under zmdc and killed
the process.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pause() did not restore last_write_index to the sentinel value
(image_buffer_count). After a Pause/Play cycle, the DECODING_ONDEMAND
fallback condition (last_write_index == image_buffer_count) was dead,
making decoding depend entirely on hasViewers(). This created a timing
gap where the decoder skipped packets after Play before zms called
setLastViewed, causing the decoder to fall behind capture.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When auth is disabled or auth_relay is empty, appending '&'+auth_relay
produces a trailing '&' which results in double '&&' when the next
parameter is appended (e.g. ?monitor=2&&scale=41&mode=single).
Guard all 4 places in MonitorStream.js where auth_relay is concatenated
into URLs, consistent with EventStream.js which already guards this.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ondisconnect() nulls this.ws, but the WebSocket open event fires
asynchronously. If ondisconnect() runs between socket creation and
the open callback, onopen() hits a null this.ws when adding the
message listener. Add a null guard to bail out cleanly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous migration relied on Zones.Area to convert pixel thresholds
to percentages, but this column is often 0 (never populated by the C++
daemon which calculates polygon area at runtime). Replace the single
UPDATE with a cursor-based stored procedure that computes the polygon
area directly from percentage coordinates using the shoelace formula,
then derives the pixel area for accurate threshold conversion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dbrow[col] for StorageId can be NULL when the database column contains
a NULL value. Passing NULL to atoi causes a segfault in strtol.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The existing PTS backward jump check in FfmpegCamera::Capture() does not
catch cases where DTS jumps back significantly (e.g. stream restarts,
encoder resets) while PTS may remain unaffected. This causes the
VideoStore to spend minutes forcing DTS monotonicity on every packet via
the write_packet fixup path, flooding logs with warnings.
Add per-stream DTS tracking (mLastVideoDTS/mLastAudioDTS) and a backward
jump check mirroring the existing PTS check: if DTS jumps back more than
10 seconds, increment error_count and after 6 occurrences return -1 to
trigger capture reconnection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bandwidth limiter in skin.php ran after config.php was included,
so the ZM_WEB_* constants were defined using the unclamped cookie
value. When no cookie existed and ZM_BANDWIDTH_DEFAULT was 'high',
a user with MaxBandwidth='medium' would get high bandwidth config
values despite the navbar showing medium.
Move the MaxBandwidth clamp before the config.php include so the
switch statement sees the correct value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each filter now has its own ExecuteInterval column, making the global
ZM_FILTER_EXECUTE_INTERVAL unused. The DB row will be cleaned up
automatically by zmupdate.pl on the next upgrade.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sleep delay was overwritten by each filter in the loop, so only the
last filter's timing controlled the sleep. Now tracks the minimum delay
across all filters so no filter oversleeps its ExecuteInterval.
Also removes the unused ZM_FILTER_EXECUTE_INTERVAL reference since each
filter has its own ExecuteInterval, and fixes the overdue warning to
check the unclamped filter_delay and include the filter name.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SQL changes every cycle due to zmDiskPercent/zmDiskBlocks/zmSystemLoad
string substitution with live values, so prepare_cached never actually
returns a cached handle. Instead it leaks one cache entry per distinct
substituted value over the process lifetime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PostSQLConditions, HasDiskPercent, HasDiskBlocks, and HasSystemLoad were
set during Sql() term processing but never cleared between rebuilds.
Each Execute() cycle pushed duplicate ExistsInFileSystem terms onto the
PostSQLConditions array, causing it to grow unboundedly over the process
lifetime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reolink cameras may return a 302 redirect from HTTP to HTTPS.
LWP::UserAgent does not follow redirects for POST requests, so the
login would fail. Detect the redirect, update protocol/host/port from
the Location header, disable SSL verification for self-signed camera
certs, and retry the POST to the HTTPS endpoint. Subsequent API calls
use the updated protocol.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move MJPEG/JPEG ahead of YUYV/UYVY in the preferred format arrays
for all color spaces. MJPEG uses far less USB bandwidth than raw YUV
while the decode cost is minimal (sw MJPEG decoder).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add "duration" string mapping in constructor and DB loader — was
silently falling back to CLOSE_IDLE
- Add CLOSE_DURATION handler in analysis logic: close event when
duration >= section_length regardless of alarm state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- The table refresh message will not appear briefly. The table will not "flicker."
Scrolling will attempt to return to the position it was in before the refresh.
When starting a stream on the Watch page, don't execute controlMute(), as this will cause the icon to be displayed before the stream actually starts playing.
We'll just set the "muted" property for the stream.
We'll manage the icon later.