Skip to content

fix(downloads): purge old processing jobs via a hosted cleanup worker#641

Open
kevinheneveld wants to merge 2 commits into
Listenarrs:canaryfrom
kevinheneveld:fix/processing-job-retention
Open

fix(downloads): purge old processing jobs via a hosted cleanup worker#641
kevinheneveld wants to merge 2 commits into
Listenarrs:canaryfrom
kevinheneveld:fix/processing-job-retention

Conversation

@kevinheneveld

Copy link
Copy Markdown
Contributor

Addresses the 3a (job retention) portion of #640.

IDownloadProcessingJobService.CleanupOldJobsAsync(retentionDays) already existed (and is implemented in EfDownloadProcessingJobRepository) but had no caller, so DownloadProcessingJobs grew unbounded on long-running instances — and every queue-snapshot reconciliation that queries this table pays for the bloat.

This adds DownloadProcessingJobCleanupService, a hosted BackgroundService that purges terminal (Completed/Failed) jobs older than a 7-day retention window shortly after startup and then daily.

Notes

  • Retention is a constant (mirrors the existing CleanupOldJobsAsync default) to keep this change migration-free; making it configurable is an easy follow-up.
  • Cleanup logic is isolated in an internal RunCleanupAsync so it can be exercised directly in tests without driving the background loop.
  • Registered alongside the other hosted services in HostedServiceRegistrationExtensions.

Tests

  • Terminal jobs past the retention window are removed while recent and non-terminal jobs are retained.
  • No-op when nothing is eligible.
  • Service is registered as a hosted service.

Does not touch the 3b (orphan terminalization) part of #640 — that's a separate, more behavioral change and is left for discussion on the issue.

@kevinheneveld kevinheneveld requested a review from a team June 1, 2026 18:43

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.

Why not handle the cleaning in the DownloadProcessingJobProcessor ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — done in a1fbb94. Dropped the standalone DownloadProcessingJobCleanupService and folded the daily purge into DownloadProcessingJobProcessor, which already owns job-table maintenance (the startup ResetStuckJobsAsync). One worker instead of two.

{
using var scope = _scopeFactory.CreateScope();
var jobService = scope.ServiceProvider.GetRequiredService<IDownloadProcessingJobService>();
await jobService.CleanupOldJobsAsync(RetentionDays);

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.

That method should be update so the actual cleaning logic does not stays in the infrastructure layer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — also in a1fbb94. The retention policy (terminal statuses + age cutoff) now lives in the application DownloadProcessingJobService; the repository method is reduced to a thin DeleteCompletedBeforeAsync(statuses, cutoffUtc) that just runs the delete it's handed.

@T4g1 T4g1 assigned kevinheneveld and unassigned T4g1 Jun 8, 2026
kevinheneveld added a commit to kevinheneveld/Listenarr that referenced this pull request Jun 8, 2026
Addresses review feedback on Listenarrs#641:

- Remove the standalone DownloadProcessingJobCleanupService and run the
  daily retention purge from DownloadProcessingJobProcessor, which already
  owns job-table maintenance (startup stuck-job reset). One worker, not two.
- Move the retention policy (terminal statuses + age cutoff) out of the
  EF repository and into the application service; the repository method is
  now a thin DeleteCompletedBeforeAsync parameterized by that policy.

Cleanup tests are rehomed to drive the processor end-to-end through the
moved policy. Behavior is unchanged: terminal jobs older than 7 days are
purged shortly after startup and then daily.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kevinheneveld added a commit to kevinheneveld/Listenarr that referenced this pull request Jun 8, 2026
Addresses review feedback on Listenarrs#641:

- Remove the standalone DownloadProcessingJobCleanupService and run the
  daily retention purge from DownloadProcessingJobProcessor, which already
  owns job-table maintenance (startup stuck-job reset). One worker, not two.
- Move the retention policy (terminal statuses + age cutoff) out of the
  EF repository and into the application service; the repository method is
  now a thin DeleteCompletedBeforeAsync parameterized by that policy.

Cleanup tests are rehomed to drive the processor end-to-end through the
moved policy. Behavior is unchanged: terminal jobs older than 7 days are
purged shortly after startup and then daily.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kevinheneveld

Copy link
Copy Markdown
Contributor Author

Thanks for the review @T4g1 — both points addressed in a1fbb94 (added on top, no force-push):

  • Folded cleanup into DownloadProcessingJobProcessor instead of a separate hosted service, since that worker already owns job-table maintenance (ResetStuckJobsAsync on startup). Removed DownloadProcessingJobCleanupService and its registration.
  • Moved the retention policy out of the infrastructure layer — terminal statuses + cutoff now live in the application DownloadProcessingJobService; the repo method is a thin DeleteCompletedBeforeAsync(statuses, cutoffUtc).

Behavior is unchanged (terminal jobs older than 7 days purged shortly after startup, then daily). Cleanup tests rehomed to drive the processor end-to-end through the moved policy.

kevinheneveld and others added 2 commits June 9, 2026 09:36
IDownloadProcessingJobService.CleanupOldJobsAsync existed but had no
caller, so the DownloadProcessingJobs table grew unbounded on
long-running instances. Every queue-snapshot reconciliation queries this
table (pending/all job download IDs for completed DDL items), so the
backlog also added steady per-snapshot cost.

Add DownloadProcessingJobCleanupService, a hosted BackgroundService that
purges terminal (Completed/Failed) jobs older than a 7-day retention
window shortly after startup and then daily. Retention is a constant to
keep this change migration-free; making it configurable is a follow-up.

Tests: terminal jobs past retention are removed while recent and
non-terminal jobs are retained; no-op when nothing is eligible; service
is registered as a hosted service.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses review feedback on Listenarrs#641:

- Remove the standalone DownloadProcessingJobCleanupService and run the
  daily retention purge from DownloadProcessingJobProcessor, which already
  owns job-table maintenance (startup stuck-job reset). One worker, not two.
- Move the retention policy (terminal statuses + age cutoff) out of the
  EF repository and into the application service; the repository method is
  now a thin DeleteCompletedBeforeAsync parameterized by that policy.

Cleanup tests are rehomed to drive the processor end-to-end through the
moved policy. Behavior is unchanged: terminal jobs older than 7 days are
purged shortly after startup and then daily.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kevinheneveld kevinheneveld force-pushed the fix/processing-job-retention branch from a1fbb94 to d8990ef Compare June 9, 2026 17:37
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.

2 participants