Add timeouts to read-only phases of CI job assignment
What does this MR do and why?
In Ci::RegisterJobService, slow CI/CD variable evaluation can eat a job request's entire 60-second Rack::Timeout. Because that timeout raises an Exception subclass rather than StandardError, it slips past the service's rescue: the build is never dropped, so it either blocks the head of the queue or, more often, is left running with no runner attached once response rendering dies mid-flight. The stuck build then only surfaces much later via the stuck-build sweep.
This MR gives each request a 55-second budget (REQUEST_TIME_BUDGET). The two read-only phases that evaluate variables — pre-assign runner checks and response rendering — run under Timeout.timeout for whatever remains of the budget, raising immediately once it's exhausted. The resulting PhaseTimeoutError is a StandardError, so the existing rescue catches it, drops the build with scheduler_failure, and lets the queue move on. State transitions stay outside the timed blocks so a timeout can never interrupt one. Guarded by the ci_register_job_phase_timeouts feature flag (request scoped actor, disabled by default).
References
- #591467
- gitlab-com/gl-infra/production#21381 (closed)
- https://gitlab.com/gitlab-org/gitlab/-/issues/616307
- #616384 (closed)
Screenshots or screen recordings
Smoke test on GDK — the same job picked up and succeeding in both flag states:
| Flag disabled (default) | Flag enabled |
|---|---|
![]() |
![]() |
How to set up and validate locally
- In a project with a runner, add a minimal CI config:
smoke: { script: ['echo ok'] }. - With the flag disabled (default), push a commit and confirm the job runs and succeeds.
- Enable the flag with
Feature.enable(:ci_register_job_phase_timeouts), push again, and confirm the job still runs and succeeds.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

