Skip to content

Commit

Permalink
use ThreadPoolExecutor to run external repos requests in parallel
Browse files Browse the repository at this point in the history
On ProjectDetail pages with several repos the lookup for private/public
status was noticably slowing the page down.  This switches to
ThreadPoolExecutor to run through them in parallel (mostly).
  • Loading branch information
ghickman committed Jan 27, 2022
1 parent dce09c6 commit 5897d25
Showing 1 changed file with 20 additions and 5 deletions.
25 changes: 20 additions & 5 deletions jobserver/views/projects.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import concurrent
import operator

import requests
from django.contrib import messages
from django.contrib.auth.decorators import login_required
Expand All @@ -19,6 +22,12 @@
from ..models import Org, Project, ProjectInvitation, ProjectMembership, Snapshot, User


# Create a global threadpool for getting repos. This lets us have a single
# pool across all requests and saves the overhead from setting up threads on
# each request.
repo_thread_pool = concurrent.futures.ThreadPoolExecutor(thread_name_prefix="get_repo")


@method_decorator(login_required, name="dispatch")
class ProjectAcceptInvite(View):
def get(self, request, *args, **kwargs):
Expand Down Expand Up @@ -125,13 +134,15 @@ def get_context_data(self, **kwargs):

workspaces = self.object.workspaces.order_by("is_archived", "name")

repos = sorted(set(workspaces.values_list("repo", flat=True)))
repos = set(workspaces.values_list("repo", flat=True))

return super().get_context_data(**kwargs) | {
"can_create_workspaces": can_create_workspaces,
"can_manage_members": can_manage_members,
"outputs": self.get_outputs(workspaces),
"repos": list(self.get_repos(repos)),
"repos": list(
sorted(self.iter_repos(repos), key=operator.itemgetter("name"))
),
"workspaces": workspaces,
}

Expand Down Expand Up @@ -161,21 +172,25 @@ def get_outputs(self, workspaces):

return Snapshot.objects.filter(pk__in=snapshot_pks).order_by("-published_at")

def get_repos(self, repo_urls):
for url in repo_urls:
def iter_repos(self, repo_urls):
def get_repo(url):
f = furl(url)

try:
is_private = get_repo_is_private(*f.path.segments)
except requests.HTTPError:
is_private = None

yield {
return {
"name": f.path.segments[1],
"url": url,
"is_private": is_private,
}

futures = [repo_thread_pool.submit(get_repo, url=url) for url in repo_urls]
for future in concurrent.futures.as_completed(futures):
yield future.result()


class ProjectInvitationCreate(CreateView):
form_class = ProjectInvitationForm
Expand Down

0 comments on commit 5897d25

Please sign in to comment.