Distinguish deploy signals from indexer failures

Description

Ai::ActiveContext::Code::Indexer shells out to the gitlab-elasticsearch-indexer binary to index a repository for code search. On gitlab.com, a deploy sends SIGTERM to the whole Sidekiq process group. That signal kills this subprocess too, since it's a child of the same group.

The code could not tell that apart from a real bug in the indexer. Gitlab::Popen.popen_with_streaming collapsed the subprocess's exit status into a bare integer, so the signal information was gone by the time Indexer#run saw it. It raised the same generic error either way. The error message could be misleading, since the last line the subprocess wrote to stderr was often just a benign log line, not the real cause. Worse, the repository ended up marked pending_retry or failed for a reason that had nothing to do with its own data.

This MR makes the code tell the two cases apart. popen_with_streaming now returns the full Process::Status, so Indexer#run can check whether a signal killed the process, and which one. When it's a signal a deploy sends, it raises a new Interrupted error instead of the generic one. Interrupted subclasses Gitlab::SidekiqMiddleware::RetryError, the pattern GitLab already uses for exactly this case: Sidekiq still retries the job as usual, but Sentry never reports it.

The two indexing services handle that the same way they already handle their own retries. InitialIndexingService puts the repository back in the state it was in before the run, so the periodic scheduler picks it up again. IncrementalIndexingService re-raises Interrupted without recording it as the repository's last_error, so Sidekiq's own retry does the rest.

A real failure, a genuine non-zero exit or a signal a deploy would never send, still raises the original error and still reports to Sentry as before.

Related to #630500 (closed).

Merge request reports

Loading
Loading