Pull mirror stuck in started for 24h when RepositoryUpdateMirrorWorker is killed mid-run
Summary
Follow-up to #477716 (closed), which handled pull mirror jobs that die before execution starts (stranding import_state in scheduled). This covers jobs that die after starting, stranding it in started — a case the 30-minute recovery path added by #477716 (closed) does not cover, so it takes up to 24 hours to clear.
Problem
RepositoryUpdateMirrorWorker sets import_state to started, fetches, then transitions to finished/failed. Clearing started is the job's own responsibility.
For large repositories an update can run long enough to overlap with Sidekiq worker restarts (deploys, autoscaling, evictions). Each restart causes Reliable Fetcher to requeue the job; after DEFAULT_MAX_RETRIES_AFTER_INTERRUPTION = 3 interruptions it is quarantined (vendor/gems/sidekiq-reliable-fetch/lib/sidekiq/base_reliable_fetch.rb:22,180-189,266).
The project is then stuck:
sidekiq_options retry: false(ee/app/workers/repository_update_mirror_worker.rb:16) — the job never runs again.import_statestaysstarted, becausefail_mirror/finish_mirrorlive inside the killed job.mirrors_to_syncexcludes%i[scheduled started](ee/app/models/ee/project.rb:288) — never rescheduled.fail_stuck_mirrors!matches onlystatus: [:scheduled](ee/app/models/ee/project.rb:299-305) — the 30-minute safety net misses it.- Recovery waits for
StuckProjectImportJobsWorker, gated on the job's Sidekiq status key expiring atIMPORT_JOBS_EXPIRATION = 24.hours(app/workers/gitlab/import/stuck_import_job.rb:10).
Retries cannot recover it
force_import_job! returns early when updating_mirror? is true, i.e. state is scheduled or started (ee/app/models/ee/project_import_state.rb:196-197,121-123). Update now and POST /projects/:id/mirror/pull are silent no-ops with no user feedback, and recreating the mirror config does not reset the state either — so every available remediation appears to succeed while changing nothing.
The eventual sweep logs Import timed out. Import took longer than 86400 seconds, but that figure is a hardcoded constant, not a measurement (app/workers/gitlab/import/stuck_import_job.rb:10,88-90). It implies a slow fetch when the job was in fact killed, which misdirects triage.
Proposal
Primary: handle interruption exhaustion. RepositoryImportWorker already does this (app/workers/repository_import_worker.rb:8,22-24):
include Sidekiq::InterruptionsExhausted
sidekiq_interruptions_exhausted do |job|
new.perform_failure(job['args'].first)
endAdding the equivalent to RepositoryUpdateMirrorWorker would mark the state failed at quarantine, triggering set_next_execution_timestamp so the scheduler retries on its next pass — avoiding the stuck state rather than shortening it.
Secondary: extend stuck_mirrors to started, keyed off last_update_started_at with its own threshold well above the longest legitimate update (the current 30-minute STUCK_JOBS_DURATION_THRESHOLD would be too aggressive). Defence in depth for processes that die without an observed interruption.
Ideas 2 and 3 in #477716 (closed) (add retries; reduce the 24-hour delay) were raised then but not implemented, and either would also have addressed this.
Related
- #477716 (closed) — parent; fixed the
scheduledhalf - #596530 —
last_errornot reset after successful runs, so the error shown during an incident can be stale and unrelated