Commit Graph
4 Commits
Author SHA1 Message Date
obscuremindandClaude Opus 5.5 ae404a0787 perf(cache): decode the bouquets only where a viewer signs in, and a cache file once
Every stream request (a segment, a key, a playlist) read and decoded the
whole bouquets map at start-up; only auth, probe and rtmp read it. On a
panel of 50 bouquets and 5,000 streams that is 3 MB and about 16 ms a
request. The three scripts load it themselves.

FileCache::get() keeps what a process last read of a file while it is still
the same file (inode, time, size): ServerRepository::getAll() has 106 call
sites, 49 of them `getAll()[$id]`, some once a table row, and each read and
decoded the servers' cache again. A value is kept only once its file's
second is over, so a file written twice within a second is never taken for
the one read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:56:23 +00:00
obscuremind cf7f5ae7a2 style: phpcs fixes and the ffprobe cache key's semgrep note
Trailing whitespace in CacheEngineCronJob and FileCacheTest's member
spacing (phpcbf). The md5() writing the ffprobe cache names a file, as
every reader of that key does; it is marked as such for semgrep's
weak-crypto rule, like the repo's other non-security md5 names.
2026-10-02 12:53:45 +00:00
Ricardo Santos e49af19417 fix(cache): atomic cache writes and safer stream/line/series regeneration
Readers (stream auth, player API, Ministra, Enigma2) read cache files with
no lock, while cron:cache_engine rewrote them in place with
file_put_contents(), and kills its own previous run with kill -9. A
request could read a truncated stream_/line_i_ entry; a killed worker left
one behind.

- FileCache::writeAtomic(): temp dotfile + rename(); set() uses it.
  The temp name no longer matches the stream_*/line_i_* entry globs.
  cleanStaleTemps() sweeps temps a killed writer left.
- cache_engine, StreamCacheBuilder, UserRepository's DB-fallback line
  cache, the balancer and ffprobe caches all write atomically.
- A failed DB read no longer counts as "deleted": streams_update /
  lines_update used to unlink the entries of every requested id when
  the query failed, and a failed full-run page let the sweep delete
  live entries. Workers now flag the failure and the sweep is skipped.
  (Database::query() leaves $db->result set on a failed execute, so the
  query's return value is checked.)
- Full-run paging is in id order (streams, lines before the MAG join,
  episodes tie-broken by stream_id): unordered LIMIT pages could skip
  rows, which the sweep then deleted.
- lines_update drops the old line_c_/line_t_ lookup files when a line's
  credentials or token change or the line is deleted.
- Incremental mode removes series_/episodes_ files of deleted series and
  of series left with no episodes (they used to keep stale lists).
- Change detection uses <= so an edit in the same second as the last
  write is not missed; the kill of older runs matches the PID exactly.
- The empty-series run's temp files are merged and removed.
- CacheCronJob's startup wipe no longer unlink()s '.' and '..'.
2026-10-02 11:59:17 +00:00
Divarion_D c370dc47fc test(core): cover FileCache and the Translator backfill
Fourth coverage batch — file-backed cache and i18n, each driven against a
throwaway temp directory:

- FileCacheTest: set/get roundtrip through serialization, miss = false, has(),
  idempotent delete, flush, maxAge expiry (backdated mtime), and the path/age
  accessors.
- TranslatorTest: English default, cookie language detection with fallback,
  available() listing, {token} substitution, guarded setLanguage(), and the
  hardened backfill — a key missing from the active language is written back
  with the English value, or with itself as a visible placeholder when English
  has none either.

+15 tests. Suite: 661 tests, 0 errors.
2026-09-13 14:45:12 +03:00