Commit a22e68bf authored by Shinya Maeda's avatar Shinya Maeda

Fix pipeline schedule edge case

If pipeline schedule is to run at the exact same time with when cron
worker runs, the pipeline schedule will not be executed at the
ideal timing.

We fix this bug by comparing the exact matching of ideal and
cron worker's next run at.
parent 97e8f494
...@@ -55,15 +55,20 @@ module Ci ...@@ -55,15 +55,20 @@ module Ci
# This way, a schedule like `*/1 * * * *` won't be triggered in a short interval # This way, a schedule like `*/1 * * * *` won't be triggered in a short interval
# when PipelineScheduleWorker runs irregularly by Sidekiq Memory Killer. # when PipelineScheduleWorker runs irregularly by Sidekiq Memory Killer.
def set_next_run_at def set_next_run_at
self.next_run_at = Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], now = Time.zone.now
Time.zone.name) ideal_next_run = ideal_next_run_from(now)
.next_time_from(ideal_next_run_at)
self.next_run_at = if ideal_next_run == cron_worker_next_run_from(now)
ideal_next_run
else
cron_worker_next_run_from(ideal_next_run)
end
end end
def schedule_next_run! def schedule_next_run!
save! # with set_next_run_at save! # with set_next_run_at
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
update_attribute(:next_run_at, nil) # update without validation update_column(:next_run_at, nil) # update without validation
end end
def job_variables def job_variables
...@@ -72,9 +77,15 @@ module Ci ...@@ -72,9 +77,15 @@ module Ci
private private
def ideal_next_run_at def ideal_next_run_from(start_time)
Gitlab::Ci::CronParser.new(cron, cron_timezone) Gitlab::Ci::CronParser.new(cron, cron_timezone)
.next_time_from(Time.zone.now) .next_time_from(start_time)
end
def cron_worker_next_run_from(start_time)
Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'],
Time.zone.name)
.next_time_from(start_time)
end end
end end
end end
---
title: Fix pipeline schedule does not run correctly when it's scheduled at the same
time with the cron worker
merge_request: 29848
author:
type: fixed
...@@ -88,23 +88,8 @@ describe Ci::PipelineSchedule do ...@@ -88,23 +88,8 @@ describe Ci::PipelineSchedule do
describe '#set_next_run_at' do describe '#set_next_run_at' do
let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) } let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) }
let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) } let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_from, Time.zone.now) }
let(:cron_worker_next_run_at) { pipeline_schedule.send(:cron_worker_next_run_from, Time.zone.now) }
let(:expected_next_run_at) do
Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
.next_time_from(ideal_next_run_at)
end
let(:cron_worker_next_run_at) do
Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
.next_time_from(Time.zone.now)
end
context 'when creates new pipeline schedule' do
it 'updates next_run_at automatically' do
expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at)
end
end
context 'when PipelineScheduleWorker runs at a specific interval' do context 'when PipelineScheduleWorker runs at a specific interval' do
before do before do
...@@ -129,7 +114,7 @@ describe Ci::PipelineSchedule do ...@@ -129,7 +114,7 @@ describe Ci::PipelineSchedule do
let(:pipeline_schedule) { create(:ci_pipeline_schedule, :every_minute) } let(:pipeline_schedule) { create(:ci_pipeline_schedule, :every_minute) }
it "updates next_run_at to the sidekiq worker's execution time" do it "updates next_run_at to the sidekiq worker's execution time" do
Timecop.freeze(2019, 06, 19, 12, 00) do Timecop.freeze(Time.parse("2019-06-01 12:18:00+0000")) do
expect(pipeline_schedule.next_run_at).to eq(cron_worker_next_run_at) expect(pipeline_schedule.next_run_at).to eq(cron_worker_next_run_at)
end end
end end
...@@ -157,9 +142,8 @@ describe Ci::PipelineSchedule do ...@@ -157,9 +142,8 @@ describe Ci::PipelineSchedule do
let(:new_cron) { '0 0 1 1 *' } let(:new_cron) { '0 0 1 1 *' }
it 'updates next_run_at automatically' do it 'updates next_run_at automatically' do
pipeline_schedule.update!(cron: new_cron) expect { pipeline_schedule.update!(cron: new_cron) }
.to change { pipeline_schedule.next_run_at }
expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at)
end end
end end
end end
...@@ -167,21 +151,24 @@ describe Ci::PipelineSchedule do ...@@ -167,21 +151,24 @@ describe Ci::PipelineSchedule do
describe '#schedule_next_run!' do describe '#schedule_next_run!' do
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) } let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) }
context 'when reschedules after 10 days from now' do before do
let(:future_time) { 10.days.from_now } pipeline_schedule.update_column(:next_run_at, nil)
let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) } end
let(:expected_next_run_at) do it 'updates next_run_at' do
Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name) expect { pipeline_schedule.schedule_next_run! }
.next_time_from(ideal_next_run_at) .to change { pipeline_schedule.next_run_at }
end
context 'when record is invalid' do
before do
allow(pipeline_schedule).to receive(:save!) { raise ActiveRecord::RecordInvalid.new(pipeline_schedule) }
end end
it 'points to proper next_run_at' do it 'nullifies the next run at' do
Timecop.freeze(future_time) do pipeline_schedule.schedule_next_run!
pipeline_schedule.schedule_next_run!
expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at) expect(pipeline_schedule.next_run_at).to be_nil
end
end end
end end
end end
......
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