Skip to content

Commit d387e04

Browse files
committed
Build: remove DEDUPLICATE_BUILDS feature
With the introduction of `CANCEL_OLD_BUILDS` we are moving away from the `DEDUPLICATE_BUILDS` feature since these scenario is managed in a better way by the new feature. Closes #7306 Related #9549
1 parent 09cdbb0 commit d387e04

File tree

9 files changed

+6
-240
lines changed

9 files changed

+6
-240
lines changed

readthedocs/builds/constants.py

-2
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,8 @@
140140
}
141141

142142
BUILD_STATUS_NORMAL = 'normal'
143-
BUILD_STATUS_DUPLICATED = 'duplicated'
144143
BUILD_STATUS_CHOICES = (
145144
(BUILD_STATUS_NORMAL, 'Normal'),
146-
(BUILD_STATUS_DUPLICATED, 'Duplicated'),
147145
)
148146

149147

readthedocs/core/utils/__init__.py

+1-64
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
"""Common utility functions."""
22

3-
import datetime
43
import re
54
import signal
65

76
import structlog
87
from django.conf import settings
9-
from django.utils import timezone
108
from django.utils.functional import keep_lazy
119
from django.utils.safestring import SafeText, mark_safe
1210
from django.utils.text import slugify as slugify_base
@@ -19,11 +17,7 @@
1917
BUILD_STATUS_PENDING,
2018
EXTERNAL,
2119
)
22-
from readthedocs.doc_builder.exceptions import (
23-
BuildCancelled,
24-
BuildMaxConcurrencyError,
25-
DuplicatedBuildError,
26-
)
20+
from readthedocs.doc_builder.exceptions import BuildCancelled, BuildMaxConcurrencyError
2721
from readthedocs.worker import app
2822

2923
log = structlog.get_logger(__name__)
@@ -147,63 +141,6 @@ def prepare_build(
147141
# we cancel all of them and trigger a new one for the latest commit received.
148142
for running_build in running_builds:
149143
cancel_build(running_build)
150-
else:
151-
# NOTE: de-duplicate builds won't be required if we enable `CANCEL_OLD_BUILDS`,
152-
# since canceling a build is more effective.
153-
# However, we are keepting `DEDUPLICATE_BUILDS` code around while we test
154-
# `CANCEL_OLD_BUILDS` with a few projects and we are happy with the results.
155-
# After that, we can remove `DEDUPLICATE_BUILDS` code
156-
# and make `CANCEL_OLD_BUILDS` the default behavior.
157-
if commit:
158-
skip_build = (
159-
Build.objects.filter(
160-
project=project,
161-
version=version,
162-
commit=commit,
163-
)
164-
.exclude(
165-
state__in=BUILD_FINAL_STATES,
166-
)
167-
.exclude(
168-
pk=build.pk,
169-
)
170-
.exists()
171-
)
172-
else:
173-
skip_build = (
174-
Build.objects.filter(
175-
project=project,
176-
version=version,
177-
state=BUILD_STATE_TRIGGERED,
178-
# By filtering for builds triggered in the previous 5 minutes we
179-
# avoid false positives for builds that failed for any reason and
180-
# didn't update their state, ending up on blocked builds for that
181-
# version (all the builds are marked as DUPLICATED in that case).
182-
# Adding this date condition, we reduce the risk of hitting this
183-
# problem to 5 minutes only.
184-
date__gte=timezone.now() - datetime.timedelta(minutes=5),
185-
).count()
186-
> 1
187-
)
188-
189-
if not project.has_feature(Feature.DEDUPLICATE_BUILDS):
190-
log.debug(
191-
"Skipping deduplication of builds. Feature not enabled.",
192-
)
193-
skip_build = False
194-
195-
if skip_build:
196-
# TODO: we could mark the old build as duplicated, however we reset our
197-
# position in the queue and go back to the end of it --penalization
198-
log.warning(
199-
"Marking build to be skipped by builder.",
200-
)
201-
build.error = DuplicatedBuildError.message
202-
build.status = DuplicatedBuildError.status
203-
build.exit_code = DuplicatedBuildError.exit_code
204-
build.success = False
205-
build.state = BUILD_STATE_CANCELLED
206-
build.save()
207144

208145
# Start the build in X minutes and mark it as limited
209146
if not skip_build and project.has_feature(Feature.LIMIT_CONCURRENT_BUILDS):

readthedocs/doc_builder/exceptions.py

+1-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from django.utils.translation import gettext_noop
44

5-
from readthedocs.builds.constants import BUILD_STATE_CANCELLED, BUILD_STATUS_DUPLICATED
5+
from readthedocs.builds.constants import BUILD_STATE_CANCELLED
66
from readthedocs.projects.constants import BUILD_COMMANDS_OUTPUT_PATH_HTML
77

88

@@ -56,13 +56,6 @@ class BuildMaxConcurrencyError(BuildUserError):
5656
message = gettext_noop('Concurrency limit reached ({limit}), retrying in 5 minutes.')
5757

5858

59-
class DuplicatedBuildError(BuildUserError):
60-
message = gettext_noop('Duplicated build.')
61-
exit_code = 1
62-
status = BUILD_STATUS_DUPLICATED
63-
state = BUILD_STATE_CANCELLED
64-
65-
6659
class BuildCancelled(BuildUserError):
6760
message = gettext_noop('Build cancelled by user.')
6861
state = BUILD_STATE_CANCELLED

readthedocs/projects/models.py

-5
Original file line numberDiff line numberDiff line change
@@ -1863,7 +1863,6 @@ def add_features(sender, **kwargs):
18631863
VCS_REMOTE_LISTING = "vcs_remote_listing"
18641864
SPHINX_PARALLEL = "sphinx_parallel"
18651865
USE_SPHINX_BUILDERS = "use_sphinx_builders"
1866-
DEDUPLICATE_BUILDS = "deduplicate_builds"
18671866
CANCEL_OLD_BUILDS = "cancel_old_builds"
18681867
DONT_CREATE_INDEX = "dont_create_index"
18691868

@@ -2012,10 +2011,6 @@ def add_features(sender, **kwargs):
20122011
USE_SPHINX_BUILDERS,
20132012
_('Use regular sphinx builders instead of custom RTD builders'),
20142013
),
2015-
(
2016-
DEDUPLICATE_BUILDS,
2017-
_('Mark duplicated builds as NOOP to be skipped by builders'),
2018-
),
20192014
(
20202015
CANCEL_OLD_BUILDS,
20212016
_(

readthedocs/projects/tasks/builds.py

-10
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
BuildCancelled,
4545
BuildMaxConcurrencyError,
4646
BuildUserError,
47-
DuplicatedBuildError,
4847
MkDocsYAMLParseError,
4948
ProjectBuildsSkippedError,
5049
YAMLParseError,
@@ -276,7 +275,6 @@ class UpdateDocsTask(SyncRepositoryMixin, Task):
276275
# All exceptions generated by a user miss-configuration should be listed
277276
# here. Actually, every subclass of ``BuildUserError``.
278277
throws = (
279-
DuplicatedBuildError,
280278
ProjectBuildsSkippedError,
281279
ConfigError,
282280
YAMLParseError,
@@ -291,14 +289,12 @@ class UpdateDocsTask(SyncRepositoryMixin, Task):
291289
exceptions_without_notifications = (
292290
BuildCancelled,
293291
BuildMaxConcurrencyError,
294-
DuplicatedBuildError,
295292
ProjectBuildsSkippedError,
296293
)
297294

298295
# Do not send external build status on failure builds for these exceptions.
299296
exceptions_without_external_build_status = (
300297
BuildMaxConcurrencyError,
301-
DuplicatedBuildError,
302298
)
303299

304300
acks_late = True
@@ -354,11 +350,6 @@ def _check_concurrency_limit(self):
354350
)
355351
)
356352

357-
def _check_duplicated_build(self):
358-
if self.data.build.get('status') == DuplicatedBuildError.status:
359-
log.warning('NOOP: build is marked as duplicated.')
360-
raise DuplicatedBuildError
361-
362353
def _check_project_disabled(self):
363354
if self.data.project.skip:
364355
log.warning('Project build skipped.')
@@ -412,7 +403,6 @@ def before_start(self, task_id, args, kwargs):
412403
self._setup_sigterm()
413404

414405
self._check_project_disabled()
415-
self._check_duplicated_build()
416406
self._check_concurrency_limit()
417407
self._reset_build()
418408

readthedocs/projects/views/public.py

+2-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"""Public project views."""
22

33
import hashlib
4-
import structlog
54
import mimetypes
65
import os
76
from collections import OrderedDict
87
from urllib.parse import urlparse
98

9+
import structlog
1010
from django.conf import settings
1111
from django.contrib import messages
1212
from django.db.models import prefetch_related_objects
@@ -22,7 +22,7 @@
2222

2323
from readthedocs.analytics.tasks import analytics_event
2424
from readthedocs.analytics.utils import get_client_ip
25-
from readthedocs.builds.constants import BUILD_STATUS_DUPLICATED, LATEST
25+
from readthedocs.builds.constants import LATEST
2626
from readthedocs.builds.models import Version
2727
from readthedocs.builds.views import BuildTriggerMixin
2828
from readthedocs.core.permissions import AdminPermission
@@ -184,20 +184,6 @@ def get(self, request, project_slug, *args, **kwargs):
184184
slug=version_slug,
185185
).first()
186186

187-
if version:
188-
last_build = (
189-
version.builds
190-
.filter(type='html', state='finished')
191-
.exclude(status=BUILD_STATUS_DUPLICATED)
192-
.order_by('-date')
193-
.first()
194-
)
195-
if last_build:
196-
if last_build.success:
197-
status = self.STATUS_PASSING
198-
else:
199-
status = self.STATUS_FAILING
200-
201187
return self.serve_badge(request, status)
202188

203189
def get_style(self, request):

readthedocs/rtd_tests/tests/test_api.py

-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
from readthedocs.builds.constants import (
4141
BUILD_STATE_CLONING,
4242
BUILD_STATE_TRIGGERED,
43-
BUILD_STATUS_DUPLICATED,
4443
EXTERNAL,
4544
EXTERNAL_VERSION_STATE_CLOSED,
4645
LATEST,
@@ -105,7 +104,6 @@ def test_reset_build(self):
105104
project=self.project,
106105
version=self.version,
107106
state=BUILD_STATE_CLONING,
108-
status=BUILD_STATUS_DUPLICATED,
109107
success=False,
110108
output='Output',
111109
error='Error',

readthedocs/rtd_tests/tests/test_builds.py

+1-113
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import datetime
2-
from unittest import mock
32

43
from django.contrib.auth.models import User
54
from django.test import TestCase
@@ -14,9 +13,7 @@
1413
GITLAB_EXTERNAL_VERSION_NAME,
1514
)
1615
from readthedocs.builds.models import Build, Version
17-
from readthedocs.core.utils import trigger_build
18-
from readthedocs.doc_builder.exceptions import DuplicatedBuildError
19-
from readthedocs.projects.models import Feature, Project
16+
from readthedocs.projects.models import Project
2017

2118

2219
class BuildModelTests(TestCase):
@@ -482,112 +479,3 @@ def test_can_rebuild_with_old_build(self):
482479

483480
self.assertFalse(old_external_build.can_rebuild)
484481
self.assertTrue(latest_external_build.can_rebuild)
485-
486-
487-
@mock.patch('readthedocs.projects.tasks.builds.update_docs_task')
488-
class DeDuplicateBuildTests(TestCase):
489-
490-
def setUp(self):
491-
self.project = get(Project)
492-
self.version = get(
493-
Version,
494-
project=self.project,
495-
type=BRANCH,
496-
)
497-
498-
get(
499-
Feature,
500-
feature_id=Feature.DEDUPLICATE_BUILDS,
501-
projects=[self.project],
502-
)
503-
504-
def test_trigger_duplicated_build_by_commit(self, update_docs_task):
505-
"""
506-
Trigger a build for the same commit twice.
507-
508-
The second build should be marked as NOOP.
509-
"""
510-
self.assertEqual(Build.objects.count(), 0)
511-
trigger_build(self.project, self.version, commit='a1b2c3')
512-
self.assertEqual(Build.objects.count(), 1)
513-
build = Build.objects.first()
514-
self.assertEqual(build.state, 'triggered')
515-
516-
trigger_build(self.project, self.version, commit='a1b2c3')
517-
self.assertEqual(Build.objects.count(), 2)
518-
build = Build.objects.first()
519-
self.assertEqual(build.error, DuplicatedBuildError.message)
520-
self.assertEqual(build.success, False)
521-
self.assertEqual(build.exit_code, DuplicatedBuildError.exit_code)
522-
self.assertEqual(build.status, DuplicatedBuildError.status)
523-
self.assertEqual(build.state, "cancelled")
524-
525-
def test_trigger_duplicated_finshed_build_by_commit(self, update_docs_task):
526-
"""
527-
Trigger a build for the same commit twice.
528-
529-
The second build should not be marked as NOOP if the previous
530-
duplicated builds are in 'finished' state.
531-
"""
532-
self.assertEqual(Build.objects.count(), 0)
533-
trigger_build(self.project, self.version, commit='a1b2c3')
534-
self.assertEqual(Build.objects.count(), 1)
535-
536-
# Mark the build as finished
537-
build = Build.objects.first()
538-
build.state = 'finished'
539-
build.save()
540-
build.refresh_from_db()
541-
542-
trigger_build(self.project, self.version, commit='a1b2c3')
543-
self.assertEqual(Build.objects.count(), 2)
544-
build = Build.objects.first()
545-
self.assertEqual(build.state, 'triggered')
546-
self.assertIsNone(build.status)
547-
548-
def test_trigger_duplicated_build_by_version(self, update_docs_task):
549-
"""
550-
Trigger a build for the same version.
551-
552-
The second build should be marked as NOOP if there is already a build
553-
for the same project and version on 'triggered' state.
554-
"""
555-
self.assertEqual(Build.objects.count(), 0)
556-
trigger_build(self.project, self.version, commit=None)
557-
self.assertEqual(Build.objects.count(), 1)
558-
build = Build.objects.first()
559-
self.assertEqual(build.state, 'triggered')
560-
561-
trigger_build(self.project, self.version, commit=None)
562-
self.assertEqual(Build.objects.count(), 2)
563-
build = Build.objects.first()
564-
self.assertEqual(build.error, DuplicatedBuildError.message)
565-
self.assertEqual(build.success, False)
566-
self.assertEqual(build.exit_code, DuplicatedBuildError.exit_code)
567-
self.assertEqual(build.status, DuplicatedBuildError.status)
568-
self.assertEqual(build.state, "cancelled")
569-
570-
def test_trigger_duplicated_non_triggered_build_by_version(self, update_docs_task):
571-
"""
572-
Trigger a build for the same version.
573-
574-
The second build should not be marked as NOOP because the previous build
575-
for the same project and version is on 'building' state (any non 'triggered')
576-
"""
577-
self.assertEqual(Build.objects.count(), 0)
578-
trigger_build(self.project, self.version, commit=None)
579-
self.assertEqual(Build.objects.count(), 1)
580-
build = Build.objects.first()
581-
self.assertEqual(build.state, 'triggered')
582-
583-
# Mark the build as building
584-
build = Build.objects.first()
585-
build.state = 'building'
586-
build.save()
587-
build.refresh_from_db()
588-
589-
trigger_build(self.project, self.version, commit=None)
590-
self.assertEqual(Build.objects.count(), 2)
591-
build = Build.objects.first()
592-
self.assertEqual(build.state, 'triggered')
593-
self.assertIsNone(build.status)

0 commit comments

Comments
 (0)