Fix idle-in-transaction during mark_as_unmergeable by deferring notify_conflict?

Summary

Move notify_conflict? and its downstream actions (NotificationService, TodoService) into a run_after_commit block so that Gitaly RPCs execute after the database transaction commits, eliminating idle-in-transaction time during the mark_as_unmergeable state transition.

Behind feature flag defer_notify_conflict_to_after_commit.

Problem

In the merge_status state machine, the after_transition [:unchecked, :checking] => :cannot_be_merged callback calls notify_conflict? inside the save transaction. notify_conflict? makes three Gitaly RPCs:

  1. source_branch_exists? → Gitaly FindBranch RPC (via branch_missing?)
  2. target_branch_exists? → Gitaly FindBranch RPC (via branch_missing?)
  3. repository.can_be_merged? → Gitaly ConflictsService RPC

These RPCs hold the DB connection idle while waiting on Gitaly responses, contributing to idle-in-transaction issues.

Additionally, TodoService.new.merge_request_became_unmergeable was also running inside the transaction unnecessarily.

Fix

Before (Gitaly RPCs inside transaction):

after_transition [:unchecked, :checking] => :cannot_be_merged do |merge_request, transition|
  if merge_request.notify_conflict?        # ← 3 Gitaly RPCs inside transaction
    merge_request.run_after_commit do
      NotificationService.new.merge_request_unmergeable(merge_request)
    end
    TodoService.new.merge_request_became_unmergeable(merge_request)  # ← inside transaction
  end
end

After (everything deferred to after commit):

after_transition [:unchecked, :checking] => :cannot_be_merged do |merge_request, transition|
  merge_request.run_after_commit do
    next unless merge_request.notify_conflict?  # ← Gitaly RPCs after commit

    NotificationService.new.merge_request_unmergeable(merge_request)
    TodoService.new.merge_request_became_unmergeable(merge_request)  # ← after commit
  end
end

Feature flag

defer_notify_conflict_to_after_commitrollout issue

Verification

Locally verified using ApplicationRecord.inside_transaction? that:

  • Before fix: notify_conflict? returns inside_transaction?: true
  • After fix: notify_conflict?, branch_missing?, and can_be_merged? all return inside_transaction?: false

This affects the MergeRequestMergeabilityCheckWorkerMergeabilityCheckServiceupdate_merge_statusmark_as_unmergeable call path.

Edited by Marc Shaw

Merge request reports

Loading