Skip to content

fix(assets): bound asset duration to prevent viewer crash-loop - #3128

Merged
vpetersson merged 5 commits into
masterfrom
fix/asset-duration-bounds
Jul 7, 2026
Merged

fix(assets): bound asset duration to prevent viewer crash-loop#3128
vpetersson merged 5 commits into
masterfrom
fix/asset-duration-bounds

Conversation

@vpetersson

@vpetersson vpetersson commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Issues Fixed

Description

An out-of-range asset duration — 9999999999999, typed by an operator to mean "forever" — reached threading.Event.wait in the viewer's asset_loop, which raises OverflowError for timeouts past C PyTime_t range. The exception propagates out of main(), so the viewer crash-looped and took the screen down (875 Sentry events from one pi5 in under a day).

The fix bounds duration at every layer, mirroring the existing REFRESH_INTERVAL_S_MAX / clamp_refresh_interval pattern:

  • DURATION_S_MAX (1 year — effectively "forever" for signage) + clamp_duration() in app.models.
  • Viewer clamps on read, so a pre-existing bad row can never crash the loop (regression test asserts the wait gets the clamped value).
  • v2 API rejects out-of-range duration, default_duration, and default_streaming_duration via IntegerField(min_value, max_value).
  • v1/v1.1/v1.2 (duration is a CharField there) get the same bounds in their parse paths.
  • HTML form handlers clamp (same clamp-not-400 policy as the refresh interval).

Validation on real hardware

Exercised on the physical testbeds (Pi 4 aarch64 and x86), not just unit tests:

  • x86 viewer, full end-to-end — injected a huge-duration (9999999999999) enabled asset straight into the DB, bypassing the API to simulate a legacy / hand-edited row, then let the live asset_loop run. The viewer displayed the asset and logged Sleeping for 31536000 (the clamped 365 days, not the raw value) with zero crashes — the container's RestartCount stayed at 0. Before the fix this exact scenario crash-looped the viewer.
  • x86 APIPOST and PATCH to /api/v2/assets with duration=9999999999999 both returned HTTP 400 (Ensure this value is less than or equal to 31536000); a sane duration still returned 201.
  • Pi 4 (aarch64) viewer container — reproduced the crash on the real arm64 image: threading.Event.wait(timeout=9999999999999)OverflowError: timestamp too large to convert to C PyTime_t (the ANTHIAS-3E crash). With the fix, clamp_duration(9999999999999)31536000 and the wait succeeds; confirmed asset_loop calls clamp_duration before the wait. (The Pi 4 testbed is display-headless so its asset_loop is display-gated — the arch-specific crash-and-fix was confirmed in the real container, and the full live-rotation e2e above ran on x86.)

Both testbeds were restored to their pinned images afterward.

Checklist

  • I have performed a self-review of my own code.
  • New and existing unit tests pass locally and on CI with my changes.
  • I have done an end-to-end test for Raspberry Pi devices.
  • I have tested my changes for x86 devices.
  • I added a documentation for the changes I have made (when necessary).

🤖 Generated with Claude Code

An out-of-range duration (e.g. 9999999999999, typed to mean
"forever") reached threading.Event.wait in the viewer, raising
OverflowError past C PyTime_t range and crash-looping the device
(Sentry ANTHIAS-3E).

- Add DURATION_S_MAX (1 year) + clamp_duration to app.models,
  mirroring the refresh-interval guard
- Viewer: clamp duration on read so pre-existing rows can't take
  the screen down
- v2 API: bound duration and default durations via IntegerField
  min/max
- v1/v1.1/v1.2: bound the CharField duration in the parse paths
- HTML forms: clamp asset duration and default durations on save

Fixes #3122

Co-Authored-By: Claude Fable 5 <[email protected]>
@vpetersson
vpetersson requested a review from a team as a code owner July 7, 2026 16:34
@vpetersson vpetersson self-assigned this Jul 7, 2026
@vpetersson
vpetersson requested a review from Copilot July 7, 2026 16:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prevents viewer crash-loops caused by out-of-range Asset.duration values reaching threading.Event.wait() (OverflowError), by introducing a shared duration cap and enforcing/clamping it across viewer read paths, server HTML form handlers, and API serializers (v1–v2), with regression tests.

Changes:

  • Add DURATION_S_MAX (1 year) and clamp_duration() helper, and clamp duration in the viewer loop to protect against pre-existing bad DB rows.
  • Enforce duration bounds on API write paths (v2 via IntegerField(min_value, max_value), v1-family via serializer parse/prepare paths) and clamp in HTML POST handlers.
  • Add unit/integration tests covering viewer clamp behavior, HTML form clamping, and API rejection of out-of-range durations.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/test_viewer.py Adds regression test asserting viewer clamps oversized duration before calling Event.wait().
tests/test_template_views.py Adds template-view test ensuring HTML assets_update clamps oversized duration instead of 400’ing.
src/anthias_viewer/init.py Clamps asset['duration'] on read in asset_loop before any waits/playback.
src/anthias_server/app/views.py Clamps posted asset duration and default durations in settings save handler.
src/anthias_server/app/models.py Introduces DURATION_S_MAX and clamp_duration() helper alongside existing refresh-interval clamp.
src/anthias_server/api/tests/test_assets.py Adds API tests ensuring out-of-range durations are rejected across create/update paths.
src/anthias_server/api/serializers/v2.py Adds min/max bounds to v2 duration/default duration integer fields.
src/anthias_server/api/serializers/v1_1.py Adds explicit duration range validation in v1.1 prepare paths.
src/anthias_server/api/serializers/mixins.py Adds duration range validation for v1.2 create paths (with a remaining unhandled non-int parse case).
src/anthias_server/api/serializers/init.py Adds shared parse_duration() + standardized range error message and hooks duration validation into updates.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/anthias_server/api/serializers/mixins.py Outdated
Copilot review: int(duration) in the v1.2 create path let a
non-integer escape as an unhandled ValueError (500) instead of a
validation error keyed on duration like the other versions.

Co-Authored-By: Claude Fable 5 <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment thread src/anthias_server/api/serializers/v1_1.py Outdated
Copilot review: get_video_duration output bypassed the new bound, so
a corrupted container header could still persist an out-of-range
duration. Clamp rather than reject — the file itself is playable.

Co-Authored-By: Claude Fable 5 <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment thread src/anthias_server/api/serializers/mixins.py
Copilot review: int(duration_raw) == 0 let a non-integer CharField
value escape as an unhandled ValueError (500) instead of a 400
keyed on duration.

Co-Authored-By: Claude Fable 5 <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment thread src/anthias_server/app/models.py
Copilot review: 292 years is ~9.2e9 seconds, not ~2.9e11.

Co-Authored-By: Claude Fable 5 <[email protected]>
@sonarqubecloud

sonarqubecloud Bot commented Jul 7, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

@vpetersson
vpetersson merged commit b375a9c into master Jul 7, 2026
10 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.

Viewer crash-loops on out-of-range asset duration: OverflowError in asset_loop wait (Sentry ANTHIAS-3E)

2 participants