Commit 705cfa20 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch '12996-cancel-redundant-pipelines' into 'master'

Cancel redundant pipelines in merge train when reconstruction happens

Closes #12996

See merge request gitlab-org/gitlab-ee!15450
parents 4f6be52d be686e12
...@@ -460,8 +460,8 @@ module Ci ...@@ -460,8 +460,8 @@ module Ci
canceled? && auto_canceled_by_id? canceled? && auto_canceled_by_id?
end end
def cancel_running def cancel_running(retries: nil)
retry_optimistic_lock(cancelable_statuses) do |cancelable| retry_optimistic_lock(cancelable_statuses, retries) do |cancelable|
cancelable.find_each do |job| cancelable.find_each do |job|
yield(job) if block_given? yield(job) if block_given?
job.cancel job.cancel
...@@ -469,10 +469,10 @@ module Ci ...@@ -469,10 +469,10 @@ module Ci
end end
end end
def auto_cancel_running(pipeline) def auto_cancel_running(pipeline, retries: nil)
update(auto_canceled_by: pipeline) update(auto_canceled_by: pipeline)
cancel_running do |job| cancel_running(retries: retries) do |job|
job.auto_canceled_by = pipeline job.auto_canceled_by = pipeline
end end
end end
......
...@@ -14,7 +14,6 @@ module MergeTrains ...@@ -14,7 +14,6 @@ module MergeTrains
@merge_request = merge_request @merge_request = merge_request
validate! validate!
pipeline_created = create_pipeline! if should_create_pipeline? pipeline_created = create_pipeline! if should_create_pipeline?
merge! if should_merge? merge! if should_merge?
...@@ -66,7 +65,8 @@ module MergeTrains ...@@ -66,7 +65,8 @@ module MergeTrains
raise ProcessError, result[:message] unless result[:status] == :success raise ProcessError, result[:message] unless result[:status] == :success
merge_train.update!(pipeline: result[:pipeline]) cancel_pipeline_for_merge_train(result[:pipeline])
update_pipeline_for_merge_train(result[:pipeline])
end end
def should_merge? def should_merge?
...@@ -113,6 +113,18 @@ module MergeTrains ...@@ -113,6 +113,18 @@ module MergeTrains
merge_train.pipeline merge_train.pipeline
end end
def cancel_pipeline_for_merge_train(new_pipeline)
pipeline_for_merge_train&.auto_cancel_running(new_pipeline, retries: 1)
rescue ActiveRecord::StaleObjectError
# Often the pipeline has already been canceled by the default cancelaltion
# mechanizm `Ci::CreatePipelineService#cancel_pending_pipelines`. In this
# case, we can ignore the exception as it's already canceled.
end
def update_pipeline_for_merge_train(pipeline)
merge_train.update!(pipeline: pipeline)
end
def merge_user def merge_user
merge_request.merge_user merge_request.merge_user
end end
......
---
title: Cancel redundant merge train pipelines
merge_request: 15450
author:
type: added
...@@ -50,6 +50,26 @@ describe MergeTrains::RefreshMergeRequestService do ...@@ -50,6 +50,26 @@ describe MergeTrains::RefreshMergeRequestService do
end end
end end
shared_examples_for 'cancels and recreates a pipeline for the merge train' do
let(:previous_ref) { 'refs/heads/master' }
it do
expect_next_instance_of(MergeTrains::CreatePipelineService, project, maintainer) do |pipeline_service|
allow(pipeline_service).to receive(:execute) { { status: :success, pipeline: create(:ci_pipeline) } }
expect(pipeline_service).to receive(:execute).with(merge_request, previous_ref)
end
result = subject
new_pipeline = merge_request.merge_train.pipeline
pipeline.reset
expect(result[:status]).to eq(:success)
expect(result[:pipeline_created]).to eq(true)
expect(pipeline.status).to eq('canceled')
expect(pipeline.auto_canceled_by_id).to eq(new_pipeline.id)
end
end
shared_examples_for 'does not create a pipeline' do shared_examples_for 'does not create a pipeline' do
it do it do
expect(service).not_to receive(:create_pipeline!) expect(service).not_to receive(:create_pipeline!)
...@@ -137,7 +157,7 @@ describe MergeTrains::RefreshMergeRequestService do ...@@ -137,7 +157,7 @@ describe MergeTrains::RefreshMergeRequestService do
end end
context 'when pipeline for merge train is running' do context 'when pipeline for merge train is running' do
let(:pipeline) { create(:ci_pipeline, :running, target_sha: previous_ref_sha, source_sha: merge_request.diff_head_sha) } let(:pipeline) { create(:ci_pipeline, :running, :with_job, project: project, target_sha: previous_ref_sha, source_sha: merge_request.diff_head_sha) }
let(:previous_ref_sha) { project.repository.commit('refs/heads/master').sha } let(:previous_ref_sha) { project.repository.commit('refs/heads/master').sha }
before do before do
...@@ -151,13 +171,13 @@ describe MergeTrains::RefreshMergeRequestService do ...@@ -151,13 +171,13 @@ describe MergeTrains::RefreshMergeRequestService do
context 'when the pipeline is stale' do context 'when the pipeline is stale' do
let(:previous_ref_sha) { project.repository.commits('refs/heads/master', limit: 2).last.sha } let(:previous_ref_sha) { project.repository.commits('refs/heads/master', limit: 2).last.sha }
it_behaves_like 'creates a pipeline for merge train' it_behaves_like 'cancels and recreates a pipeline for the merge train'
end end
context 'when the pipeline is reuired to be recreated' do context 'when the pipeline is required to be recreated' do
let(:require_recreate) { true } let(:require_recreate) { true }
it_behaves_like 'creates a pipeline for merge train' it_behaves_like 'cancels and recreates a pipeline for the merge train'
end end
end end
......
...@@ -4,7 +4,8 @@ module Gitlab ...@@ -4,7 +4,8 @@ module Gitlab
module OptimisticLocking module OptimisticLocking
module_function module_function
def retry_lock(subject, retries = 100, &block) def retry_lock(subject, retries = nil, &block)
retries ||= 100
# TODO(Observability): We should be recording details of the number of retries and the duration of the total execution here # TODO(Observability): We should be recording details of the number of retries and the duration of the total execution here
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
yield(subject) yield(subject)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment