Skip to content

feat: add update failure backoff and entrypoint permission checks - #389

Open
rtuszik wants to merge 3 commits into
devfrom
fix-backoff
Open

rtuszik wants to merge 3 commits into
devfrom
fix-backoff

Conversation

@rtuszik

@rtuszik rtuszik commented Oct 13, 2026

Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 323946a3-d0c9-419e-a42d-b892c5a5ea74
📥 Commits

Reviewing files that changed from the base of the PR and between 7266666 and 756e77f.

📒 Files selected for processing: 1
  • tests/test_process_manager.py

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.

📝 Walkthrough

The update manager now records attempt state and applies exponential retry delays that persist across restarts. The container entrypoint adds user and data-directory checks. Compose uses the latest Photon image tag without a pinned digest.

Changes: Update retry scheduling

Layer / File(s) Summary
Persist update attempt state
src/index.py, src/process_manager.py, tests/test_index.py, tests/test_process_manager.py
The index module reads and writes update-attempt markers. The manager returns explicit outcomes for update runs and tracks attempt state. Tests cover marker handling and outcome values.
Restore and apply retry backoff
src/process_manager.py, tests/test_process_manager.py, README.md
The scheduler restores persisted attempt state and applies exponential delays up to 24 hours. Tests cover throttling, failure counts, recovery, and age-check errors. The README documents the retry delays.

Container startup checks

Layer / File(s) Summary
Validate startup user and data access
entrypoint.sh, README.md
Root startup exits if it cannot resolve the Photon user or group. Non-root startup warns if PUID or PGID is set and exits if an existing data directory is not writable. The README describes non-root use and data ownership.

Image reference and documentation

Layer / File(s) Summary
Set image reference and document related updates
docker-compose.yml, renovate.json, README.md
Compose uses the latest Photon image tag, and Renovate disables digest pinning for the Compose file. The README notes renamed request timer metrics and labels the API example as text.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Merge Risk: ⚪ Minimal · up to 756e7

No actionable issue is identified; the change is mergeable after normal checks.

Pre-merge checks: Passed 3 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Description check Inconclusive No pull request description was provided, so its relevance to the changeset cannot be assessed. Add a concise description of the update failure backoff and entrypoint permission checks.
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly summarises the two main changes: update failure backoff and entrypoint permission checks.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rtuszik
rtuszik changed the base branch from main to dev October 13, 2026 18:16
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report
FileStmtsMissCoverMissing
src
   downloader.py2601993%97–98, 205, 232–233, 250, 263–274, 277–278, 317, 380
   importer.py57296%19, 30
   index.py121199%169
   process_manager.py2821196%168–169, 245, 290–291, 304–306, 396–398
   update.py131298%93, 172
src/jsonl
   decompressor.py5340%5–7
   downloader.py311455%14, 18, 23, 25, 32–46
src/utils
   config.py55395%71–73
   regions.py105892%407, 420, 428, 432, 435, 448, 459, 474
   validate_config.py50492%33, 35, 41–42
TOTAL13266795% 

Tests Skipped Failures Errors Time
314 0 💤 0 ❌ 0 🔥 1.739s ⏱️

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 613eeb97-9166-4a0a-8fbb-1519e0be2476
📥 Commits

Reviewing files that changed from the base of the PR and between b2fd19d and 7266666.

📒 Files selected for processing (8)
  • README.md
  • docker-compose.yml
  • entrypoint.sh
  • renovate.json
  • src/index.py
  • src/process_manager.py
  • tests/test_index.py
  • tests/test_process_manager.py

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

Comment thread src/process_manager.py
Comment on lines +326 to +334
def _record_attempt(self, outcome: UpdateOutcome):
failed = outcome is UpdateOutcome.FAILED
if outcome is UpdateOutcome.UPDATED and index.age_seconds() >= config.parse_interval(config.UPDATE_INTERVAL):
logger.error("Index timestamp did not advance after the update, treating the attempt as failed")
failed = True

self._update_failures = self._update_failures + 1 if failed else 0
self._last_attempt_at = time.time()
index.record_update_attempt(self._update_failures)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '295,355p' src/process_manager.py
sed -n '1,115p' src/index.py
rg -n 'def age_seconds|_maybe_update|_record_attempt|POLL_BACKOFF_SECONDS' src/process_manager.py src/index.py tests/test_process_manager.py

Repository: rtuszik/photon-docker

Length of output: 8610


🏁 Script executed:

sed -n '1,80p' src/process_manager.py
sed -n '230,345p' src/process_manager.py
sed -n '455,635p' tests/test_process_manager.py
git diff --no-ext-diff --unified=30 52856fcbc2c1a1a1126270c5a8ccce22b648ac91 726666664a8d2f5b43de9b9a180ce8aa8fccc0af -- src/process_manager.py src/index.py tests/test_process_manager.py

Repository: rtuszik/photon-docker

Length of output: 42026


🏁 Script executed:

printf '%s\n' '--- process manager ---'
sed -n '1,80p' src/process_manager.py
sed -n '230,345p' src/process_manager.py
printf '%s\n' '--- tests ---'
sed -n '455,635p' tests/test_process_manager.py
printf '%s\n' '--- diff ---'
git diff --no-ext-diff --unified=30 52856fcbc2c1a1a1126270c5a8ccce22b648ac91 726666664a8d2f5b43de9b9a180ce8aa8fccc0af -- src/process_manager.py src/index.py tests/test_process_manager.py

Repository: rtuszik/photon-docker

Length of output: 42276


Record an UPDATED attempt when index age cannot be checked.

index.age_seconds() can raise OSError after run_update() returns UPDATED. _maybe_update catches the exception only after _record_attempt exits, so the attempt state remains unchanged. If the update remains due, the next poll can retry without the intended backoff.

Treat an unverified age as not stale, then record the UPDATED attempt.

Suggested fix
--- "a/src/process_manager.py"
+++ "b/src/process_manager.py"
@@ -323,11 +323,17 @@
         except Exception:
             logger.exception("Scheduled update check failed")
 
     def _record_attempt(self, outcome: UpdateOutcome):
         failed = outcome is UpdateOutcome.FAILED
-        if outcome is UpdateOutcome.UPDATED and index.age_seconds() >= config.parse_interval(config.UPDATE_INTERVAL):
-            logger.error("Index timestamp did not advance after the update, treating the attempt as failed")
-            failed = True
+        if outcome is UpdateOutcome.UPDATED:
+            try:
+                stale = index.age_seconds() >= config.parse_interval(config.UPDATE_INTERVAL)
+            except OSError as e:
+                logger.warning(f"Could not verify index age after update: {e}")
+                stale = False
+            if stale:
+                logger.error("Index timestamp did not advance after the update, treating the attempt as failed")
+                failed = True
 
         self._update_failures = self._update_failures + 1 if failed else 0
         self._last_attempt_at = time.time()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _record_attempt(self, outcome: UpdateOutcome):
failed = outcome is UpdateOutcome.FAILED
if outcome is UpdateOutcome.UPDATED and index.age_seconds() >= config.parse_interval(config.UPDATE_INTERVAL):
logger.error("Index timestamp did not advance after the update, treating the attempt as failed")
failed = True
self._update_failures = self._update_failures + 1 if failed else 0
self._last_attempt_at = time.time()
index.record_update_attempt(self._update_failures)
def _record_attempt(self, outcome: UpdateOutcome):
failed = outcome is UpdateOutcome.FAILED
if outcome is UpdateOutcome.UPDATED:
try:
stale = index.age_seconds() >= config.parse_interval(config.UPDATE_INTERVAL)
except OSError as e:
logger.warning(f"Could not verify index age after update: {e}")
stale = False
if stale:
logger.error("Index timestamp did not advance after the update, treating the attempt as failed")
failed = True
self._update_failures = self._update_failures + 1 if failed else 0
self._last_attempt_at = time.time()
index.record_update_attempt(self._update_failures)

@rtuszik
rtuszik added this pull request to stack #391 October 15, 2026 08:33
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.

1 participant