Просмотр исходного кода

fix(releases) Handle release lock in worker task (#21994)

When another worker has the release commit lock we shouldn't fail the
task. Instead log it and return early as we will rely on the other task
completing its work.

Fixes SENTRY-JG0
Mark Story 4 лет назад
Родитель
Сommit
70117209cf
2 измененных файлов с 45 добавлено и 1 удалено
  1. 16 1
      src/sentry/tasks/commits.py
  2. 29 0
      tests/sentry/tasks/test_commits.py

+ 16 - 1
src/sentry/tasks/commits.py

@@ -11,6 +11,7 @@ from sentry.models import (
     Deploy,
     LatestRepoReleaseEnvironment,
     Release,
+    ReleaseCommitError,
     ReleaseHeadCommit,
     Repository,
     User,
@@ -173,7 +174,21 @@ def fetch_commits(release_id, user_id, refs, prev_release_id=None, **kwargs):
             commit_list.extend(repo_commits)
 
     if commit_list:
-        release.set_commits(commit_list)
+        try:
+            release.set_commits(commit_list)
+        except ReleaseCommitError:
+            # Another task or webworker is currently setting commits on this
+            # release. Return early as that task will do the remaining work.
+            logger.info(
+                "fetch_commits.duplicate",
+                extra={
+                    "release_id": release.id,
+                    "organization_id": release.organization_id,
+                    "user_id": user_id,
+                },
+            )
+            return
+
         deploys = Deploy.objects.filter(
             organization_id=release.organization_id, release=release, notified=False
         ).values_list("id", "environment_id", "date_finished")

+ 29 - 0
tests/sentry/tasks/test_commits.py

@@ -4,6 +4,7 @@ from django.core import mail
 from sentry.utils.compat.mock import patch
 from social_auth.models import UserSocialAuth
 
+from sentry.app import locks
 from sentry.exceptions import InvalidIdentity, PluginError
 from sentry.models import (
     Commit,
@@ -72,6 +73,34 @@ class FetchCommitsTest(TestCase):
         assert latest_repo_release_environment.release_id == release2.id
         assert latest_repo_release_environment.commit_id == commit_list[0].id
 
+    def test_release_locked(self):
+        self.login_as(user=self.user)
+        org = self.create_organization(owner=self.user, name="baz")
+        repo = Repository.objects.create(name="example", provider="dummy", organization_id=org.id)
+
+        old_release = Release.objects.create(organization_id=org.id, version="abcabcabc")
+        commit = Commit.objects.create(organization_id=org.id, repository_id=repo.id, key="a" * 40)
+        ReleaseHeadCommit.objects.create(
+            organization_id=org.id, repository_id=repo.id, release=old_release, commit=commit
+        )
+
+        refs = [{"repository": repo.name, "commit": "b" * 40}]
+        new_release = Release.objects.create(organization_id=org.id, version="12345678")
+
+        lock = locks.get(Release.get_lock_key(org.id, new_release.id), duration=10)
+        lock.acquire()
+
+        with self.tasks():
+            fetch_commits(
+                release_id=new_release.id,
+                user_id=self.user.id,
+                refs=refs,
+                previous_release_id=old_release.id,
+            )
+        count_query = ReleaseHeadCommit.objects.filter(release=new_release)
+        # No release commits should be made as the task should return early.
+        assert count_query.count() == 0
+
     @patch("sentry.tasks.commits.handle_invalid_identity")
     @patch("sentry.plugins.providers.dummy.repository.DummyRepositoryProvider.compare_commits")
     def test_fetch_error_invalid_identity(self, mock_compare_commits, mock_handle_invalid_identity):