Skip to content

Commit c26e675

Browse files
authored
Merge pull request #1417 from Shopify/retry-github-sync-job
Retry GithubSyncJob if new commit sha is missing in Github response on push from target branch
2 parents 5cf498b + 4069b34 commit c26e675

File tree

8 files changed

+74
-10
lines changed

8 files changed

+74
-10
lines changed

app/jobs/shipit/github_sync_job.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class GithubSyncJob < BackgroundJob
77
attr_reader :stack
88

99
MAX_FETCHED_COMMITS = 25
10+
MAX_RETRY_ATTEMPTS = 5
11+
RETRY_DELAY = 5.seconds
1012
queue_as :default
1113
on_duplicate :drop
1214

@@ -15,10 +17,19 @@ class GithubSyncJob < BackgroundJob
1517

1618
def perform(params)
1719
@stack = Stack.find(params[:stack_id])
20+
expected_head_sha = params[:expected_head_sha]
21+
retry_count = params[:retry_count] || 0
1822

1923
handle_github_errors do
2024
new_commits, shared_parent = fetch_missing_commits { stack.github_commits }
2125

26+
# Retry on Github eventual consistency: webhook indicated new commits but we found none
27+
if expected_head_sha && new_commits.empty? && !commit_exists?(expected_head_sha) &&
28+
retry_count < MAX_RETRY_ATTEMPTS
29+
GithubSyncJob.set(wait: RETRY_DELAY * retry_count).perform_later(params.merge(retry_count: retry_count + 1))
30+
return
31+
end
32+
2233
stack.transaction do
2334
shared_parent&.detach_children!
2435
appended_commits = new_commits.map do |gh_commit|
@@ -63,5 +74,9 @@ def handle_github_errors
6374
def lookup_commit(sha)
6475
stack.commits.find_by(sha:)
6576
end
77+
78+
def commit_exists?(sha)
79+
stack.commits.exists?(sha:)
80+
end
6681
end
6782
end

app/models/shipit/stack.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,8 @@ def recent_deploys_durations
602602
tasks.where(type: 'Shipit::Deploy').success.order(id: :desc).limit(100).durations
603603
end
604604

605-
def sync_github
606-
GithubSyncJob.perform_later(stack_id: id)
605+
def sync_github(expected_head_sha: nil)
606+
GithubSyncJob.perform_later(stack_id: id, expected_head_sha:)
607607
end
608608

609609
def links

app/models/shipit/webhooks/handlers/push_handler.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ module Handlers
66
class PushHandler < Handler
77
params do
88
requires :ref
9+
requires :after
910
end
11+
1012
def process
1113
stacks
1214
.not_archived
1315
.where(branch:)
14-
.find_each(&:sync_github)
16+
.find_each { |stack| stack.sync_github(expected_head_sha: params.after) }
1517
end
1618

1719
private

test/controllers/webhooks_controller_test.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ class WebhooksControllerTest < ActionController::TestCase
2323
test ":push with the target branch queues a GithubSyncJob" do
2424
request.headers['X-Github-Event'] = 'push'
2525

26-
body = JSON.parse(payload(:push_master)).to_json
27-
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id]) do
28-
post :create, body:, as: :json
26+
parsed_body = JSON.parse(payload(:push_master))
27+
expected_head_sha = parsed_body["after"]
28+
29+
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id, expected_head_sha:]) do
30+
post :create, body: parsed_body.to_json, as: :json
2931
end
3032
end
3133

test/jobs/github_sync_job_test.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,50 @@ class GithubSyncJobTest < ActiveSupport::TestCase
118118

119119
assert_equal true, @stack.reload.inaccessible_since?
120120
end
121+
122+
test "#perform retries when expected_head_sha is not found in the github response" do
123+
expected_sha = "abcd1234"
124+
Stack.any_instance.expects(:github_commits).returns(@github_commits)
125+
@job.expects(:fetch_missing_commits).yields.returns([[], nil])
126+
@job.expects(:commit_exists?).with(expected_sha).returns(false)
127+
128+
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id, expected_head_sha: expected_sha, retry_count: 1]) do
129+
@job.perform(stack_id: @stack.id, expected_head_sha: expected_sha)
130+
end
131+
end
132+
133+
test "#perform stops retrying after MAX_RETRY_ATTEMPTS" do
134+
expected_sha = "abcd1234"
135+
Stack.any_instance.expects(:github_commits).returns(@github_commits)
136+
@job.expects(:fetch_missing_commits).yields.returns([[], nil])
137+
@job.expects(:commit_exists?).with(expected_sha).returns(false)
138+
139+
assert_no_enqueued_jobs(only: GithubSyncJob) do
140+
@job.perform(stack_id: @stack.id, expected_head_sha: expected_sha, retry_count: GithubSyncJob::MAX_RETRY_ATTEMPTS)
141+
end
142+
end
143+
144+
test "#perform processes normally when expected_head_sha exists" do
145+
expected_sha = "abcd1234"
146+
Stack.any_instance.expects(:github_commits).returns(@github_commits)
147+
@job.expects(:fetch_missing_commits).yields.returns([[], nil])
148+
@job.expects(:commit_exists?).with(expected_sha).returns(true)
149+
150+
assert_enqueued_with(job: CacheDeploySpecJob, args: [@stack]) do
151+
@job.perform(stack_id: @stack.id, expected_head_sha: expected_sha)
152+
end
153+
end
154+
155+
test "#perform processes normally when new commits are found" do
156+
expected_sha = "abcd1234"
157+
new_commit = stub(sha: expected_sha)
158+
Stack.any_instance.expects(:github_commits).returns(@github_commits)
159+
@job.expects(:fetch_missing_commits).yields.returns([[new_commit], nil])
160+
@job.expects(:append_commit).with(new_commit).returns(stub(revert?: false))
161+
162+
assert_enqueued_with(job: CacheDeploySpecJob, args: [@stack]) do
163+
@job.perform(stack_id: @stack.id, expected_head_sha: expected_sha)
164+
end
165+
end
121166
end
122167
end

test/models/shipit/review_stack_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class ReviewStackTest < ActiveSupport::TestCase
7070
stack.archive!(shipit_users(:codertocat))
7171
end
7272

73-
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: stack.id]) do
73+
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: stack.id, expected_head_sha: nil]) do
7474
stack.unarchive!
7575
end
7676
end

test/models/shipit/stack_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -997,13 +997,13 @@ def self.deliver(event, stack, payload)
997997
@stack.archive!(shipit_users(:codertocat))
998998
end
999999

1000-
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id]) do
1000+
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id, expected_head_sha: nil]) do
10011001
@stack.unarchive!
10021002
end
10031003
end
10041004

10051005
test "#update that changes the branch name triggers a GithubSync job" do
1006-
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id]) do
1006+
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id, expected_head_sha: nil]) do
10071007
@stack.update!(branch: 'test')
10081008
end
10091009
end

test/models/shipit/webhooks/handlers/pull_request/review_stack_adapter_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ReviewStackAdapterTest < ActiveSupport::TestCase
2626
scope: stack.repository.stacks
2727
)
2828

29-
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: stack.id]) do
29+
assert_enqueued_with(job: GithubSyncJob, args: [stack_id: stack.id, expected_head_sha: nil]) do
3030
review_stack.unarchive!
3131
end
3232
end

0 commit comments

Comments
 (0)