Skip to content

Commit fac0012

Browse files
agjohnsonericholscher
authored andcommitted
Refactor project documentation syncing tasks (#3143)
This updates the syncing tasks to execute in a guaranteed fashion. Before this change, it was possible for broadcast tasks and symlink deletion tasks to execute concurrently. This changes all the broadcast task calls into function calls from a singular broadcast task. In more detail: * The `finish_build` task was executed as a task from the webs, and kicked off broadcast tasks to update files. Instead, Version update operations were move to the build process, through the API. The webs only the one broadcast task * Version API was given the same APIRestrictedPermission as other end points, it can now be updated by staff
1 parent dfcf78e commit fac0012

File tree

2 files changed

+76
-39
lines changed

2 files changed

+76
-39
lines changed

readthedocs/projects/tasks.py

+73-37
Original file line numberDiff line numberDiff line change
@@ -214,17 +214,14 @@ def run_build(self, docker=False, record=True):
214214
except SoftTimeLimitExceeded:
215215
raise BuildEnvironmentError(_('Build exited due to time out'))
216216

217-
# Web Server Tasks
217+
# Finalize build and update web servers
218218
if build_id:
219-
finish_build.delay(
220-
version_pk=self.version.pk,
221-
build_pk=build_id,
222-
hostname=socket.gethostname(),
223-
html=outcomes['html'],
224-
search=outcomes['search'],
225-
localmedia=outcomes['localmedia'],
226-
pdf=outcomes['pdf'],
227-
epub=outcomes['epub'],
219+
self.update_app_instances(
220+
html=bool(outcomes['html']),
221+
search=bool(outcomes['search']),
222+
localmedia=bool(outcomes['localmedia']),
223+
pdf=bool(outcomes['pdf']),
224+
epub=bool(outcomes['epub']),
228225
)
229226

230227
if self.build_env.failed:
@@ -333,6 +330,51 @@ def update_documentation_type(self):
333330
api_v2.project(self.project.pk).put(project_data)
334331
self.project.documentation_type = ret
335332

333+
def update_app_instances(self, html=False, localmedia=False, search=False,
334+
pdf=False, epub=False):
335+
"""Update application instances with build artifacts
336+
337+
This triggers updates across application instances for html, pdf, epub,
338+
downloads, and search. Tasks are broadcast to all web servers from here.
339+
"""
340+
# Update version if we have successfully built HTML output
341+
try:
342+
if html:
343+
version = api_v2.version(self.version.pk)
344+
version.patch({
345+
'active': True,
346+
'built': True,
347+
})
348+
except HttpClientError as e:
349+
log.error('Updating version failed, skipping file sync: version=%s',
350+
self.version.pk, exc_info=True)
351+
else:
352+
# Broadcast finalization steps to web application instances
353+
broadcast(
354+
type='app',
355+
task=sync_files,
356+
args=[
357+
self.project.pk,
358+
self.version.pk,
359+
],
360+
kwargs=dict(
361+
hostname=socket.gethostname(),
362+
html=html,
363+
localmedia=localmedia,
364+
search=search,
365+
pdf=pdf,
366+
epub=epub,
367+
)
368+
)
369+
370+
# Delayed tasks
371+
# TODO these should be chained on to the broadcast calls. The
372+
# broadcast calls could be lumped together into a promise, and on
373+
# task result, these next few tasks can be updated, also in a
374+
# chained fashion
375+
fileify.delay(self.version.pk, commit=self.build.get('commit'))
376+
update_search.delay(self.version.pk, commit=self.build.get('commit'))
377+
336378
def setup_environment(self):
337379
"""
338380
Build the virtualenv and install the project into it.
@@ -541,41 +583,35 @@ def update_imported_docs(version_pk):
541583

542584
# Web tasks
543585
@task(queue='web')
544-
def finish_build(version_pk, build_pk, hostname=None, html=False,
545-
localmedia=False, search=False, pdf=False, epub=False):
546-
"""Build Finished, do house keeping bits"""
547-
version = Version.objects.get(pk=version_pk)
548-
build = Build.objects.get(pk=build_pk)
549-
550-
if html:
551-
version.active = True
552-
version.built = True
553-
version.save()
586+
def sync_files(project_pk, version_pk, hostname=None, html=False,
587+
localmedia=False, search=False, pdf=False, epub=False):
588+
"""Sync build artifacts to application instances
554589
590+
This task broadcasts from a build instance on build completion and
591+
performs synchronization of build artifacts on each application instance.
592+
"""
593+
# Clean up unused artifacts
555594
if not pdf:
556-
broadcast(type='app', task=clear_pdf_artifacts, args=[version.pk])
595+
clear_pdf_artifacts(version_pk)
557596
if not epub:
558-
broadcast(type='app', task=clear_epub_artifacts, args=[version.pk])
597+
clear_epub_artifacts(version_pk)
559598

560599
# Sync files to the web servers
561-
broadcast(type='app', task=move_files, args=[version_pk, hostname],
562-
kwargs=dict(
563-
html=html,
564-
localmedia=localmedia,
565-
search=search,
566-
pdf=pdf,
567-
epub=epub,
568-
))
600+
move_files(
601+
version_pk,
602+
hostname,
603+
html=html,
604+
localmedia=localmedia,
605+
search=search,
606+
pdf=pdf,
607+
epub=epub
608+
)
569609

570-
# Symlink project on every web
571-
broadcast(type='app', task=symlink_project, args=[version.project.pk])
610+
# Symlink project
611+
symlink_project(project_pk)
572612

573613
# Update metadata
574-
broadcast(type='app', task=update_static_metadata, args=[version.project.pk])
575-
576-
# Delayed tasks
577-
fileify.delay(version.pk, commit=build.commit)
578-
update_search.delay(version.pk, commit=build.commit)
614+
update_static_metadata(project_pk)
579615

580616

581617
@task(queue='web')

readthedocs/restapi/views/model_views.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,9 @@ def sync_versions(self, request, **kwargs):
156156
})
157157

158158

159-
class VersionViewSet(viewsets.ReadOnlyModelViewSet):
160-
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
159+
class VersionViewSet(viewsets.ModelViewSet):
160+
161+
permission_classes = [APIRestrictedPermission]
161162
renderer_classes = (JSONRenderer,)
162163
serializer_class = VersionSerializer
163164
model = Version

0 commit comments

Comments
 (0)