Skip to content

Commit

Permalink
Serialize JournalEntry creation using advisory locks
Browse files Browse the repository at this point in the history
  • Loading branch information
dstufft committed May 21, 2024
1 parent 15a9a6c commit a5154a0
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 25 deletions.
3 changes: 2 additions & 1 deletion warehouse/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1252,7 +1252,8 @@ def _error(message):
request.db.add(Role(user=user, project=project, role_name=desired_role))
request.db.delete(role_invite)
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"accepted {desired_role} {user.username}",
submitted_by=request.user,
Expand Down
6 changes: 4 additions & 2 deletions warehouse/admin/views/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,8 @@ def add_role(project, request):
)

request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"add {role_name} {user.username}",
submitted_by=request.user,
Expand Down Expand Up @@ -642,7 +643,8 @@ def delete_role(project, request):
queue="success",
)
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"remove {role.role_name} {role.user.username}",
submitted_by=request.user,
Expand Down
6 changes: 4 additions & 2 deletions warehouse/admin/views/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ def _nuke_user(user, request):
)
for project in projects:
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action="remove project",
submitted_by=request.user,
Expand Down Expand Up @@ -242,7 +243,8 @@ def _nuke_user(user, request):
# Delete the user
request.db.delete(user)
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=f"user:{user.username}",
action="nuke user",
submitted_by=request.user,
Expand Down
6 changes: 4 additions & 2 deletions warehouse/forklift/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,8 @@ def file_upload(request):
# a SQLAlchemy hook or the like instead of doing it inline in
# this view.
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=release.project.name,
version=release.version,
action="new release",
Expand Down Expand Up @@ -1123,7 +1124,8 @@ def file_upload(request):
# SQLAlchemy hook or the like instead of doing it inline in this
# view.
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=release.project.name,
version=release.version,
action="add {python_version} file {filename}".format(
Expand Down
30 changes: 20 additions & 10 deletions warehouse/manage/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1992,7 +1992,8 @@ def yank_project_release(self):
)

self.request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
self.request.db,
name=self.release.project.name,
action="yank release",
version=self.release.version,
Expand Down Expand Up @@ -2077,7 +2078,8 @@ def unyank_project_release(self):
)

self.request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
self.request.db,
name=self.release.project.name,
action="unyank release",
version=self.release.version,
Expand Down Expand Up @@ -2178,7 +2180,8 @@ def delete_project_release(self):
)

self.request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
self.request.db,
name=self.release.project.name,
action="remove release",
version=self.release.version,
Expand Down Expand Up @@ -2270,7 +2273,8 @@ def _error(message):
)

self.request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
self.request.db,
name=self.release.project.name,
action=f"remove file {release_file.filename}",
version=self.release.version,
Expand Down Expand Up @@ -2429,7 +2433,8 @@ def manage_project_roles(project, request, _form_class=CreateRoleForm):

# Add journal entry.
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"add {role_name.value} {team_name}",
submitted_by=request.user,
Expand Down Expand Up @@ -2539,7 +2544,8 @@ def manage_project_roles(project, request, _form_class=CreateRoleForm):

# Add journal entry.
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"add {role_name} {user.username}",
submitted_by=request.user,
Expand Down Expand Up @@ -2663,7 +2669,8 @@ def manage_project_roles(project, request, _form_class=CreateRoleForm):
)

request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"invite {role_name} {username}",
submitted_by=request.user,
Expand Down Expand Up @@ -2748,7 +2755,8 @@ def revoke_project_role_invitation(project, request, _form_class=ChangeRoleForm)
role_name = token_data.get("desired_role")

request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"revoke_invite {role_name} {user.username}",
submitted_by=request.user,
Expand Down Expand Up @@ -2810,7 +2818,8 @@ def change_project_role(project, request, _form_class=ChangeRoleForm):
request.session.flash("Cannot remove yourself as Owner", queue="error")
else:
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action="change {} {} to {}".format(
role.role_name, role.user.username, form.role_name.data
Expand Down Expand Up @@ -2896,7 +2905,8 @@ def delete_project_role(project, request):
else:
request.db.delete(role)
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"remove {role.role_name} {role.user.username}",
submitted_by=request.user,
Expand Down
6 changes: 4 additions & 2 deletions warehouse/manage/views/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,8 @@ def add_organization_project(self):
if role:
self.request.db.delete(role)
self.request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
self.request.db,
name=project.name,
action=f"remove {role.role_name} {role.user.username}",
submitted_by=self.request.user,
Expand Down Expand Up @@ -1521,7 +1522,8 @@ def transfer_organization_project(project, request):
if role:
request.db.delete(role)
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"remove {role.role_name} {role.user.username}",
submitted_by=request.user,
Expand Down
6 changes: 4 additions & 2 deletions warehouse/manage/views/teams.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,8 @@ def change_team_project_role(project, request, _form_class=ChangeTeamProjectRole
else:
# Add journal entry.
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action="change {} {} to {}".format(
role.role_name.value,
Expand Down Expand Up @@ -630,7 +631,8 @@ def delete_team_project_role(project, request):

# Add journal entry.
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"remove {role_name.value} {team.name}",
submitted_by=request.user,
Expand Down
11 changes: 11 additions & 0 deletions warehouse/packaging/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from __future__ import annotations

import enum
import hashlib
import typing

from collections import OrderedDict
Expand All @@ -32,9 +33,11 @@
String,
Text,
UniqueConstraint,
cast,
func,
or_,
orm,
select,
sql,
)
from sqlalchemy.dialects.postgresql import ARRAY, CITEXT, ENUM, UUID as PG_UUID
Expand Down Expand Up @@ -859,6 +862,14 @@ def __table_args__(cls): # noqa
)
submitted_by: Mapped[User] = orm.relationship(lazy="raise_on_sql")

@classmethod
def create_with_lock(cls, session, *args, **kwargs):
hashed = hashlib.blake2b(b"table:journals").digest()[:8]
key = int.from_bytes(hashed, "little", signed=True)
session.execute(select(func.pg_advisory_xact_lock(cast(key, BigInteger))))

return cls(*args, **kwargs)


class ProhibitedProjectName(db.Model):
__tablename__ = "prohibited_project_names"
Expand Down
6 changes: 4 additions & 2 deletions warehouse/packaging/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,8 @@ def create_project(
# SQLAlchemy hook or the like instead of doing it inline in this
# service.
self.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action="create",
submitted_by=creator,
Expand All @@ -566,7 +567,8 @@ def create_project(
# SQLAlchemy hook or the like instead of doing it inline in this
# service.
self.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action=f"add Owner {creator.username}",
submitted_by=creator,
Expand Down
6 changes: 4 additions & 2 deletions warehouse/utils/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ def remove_project(project, request, flash=True):
# some kind of garbage collection at some point.

request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action="remove project",
submitted_by=request.user,
Expand All @@ -86,7 +87,8 @@ def remove_project(project, request, flash=True):

def destroy_docs(project, request, flash=True):
request.db.add(
JournalEntry(
JournalEntry.create_with_lock(
request.db,
name=project.name,
action="docdestroy",
submitted_by=request.user,
Expand Down

0 comments on commit a5154a0

Please sign in to comment.