send_email: Change clear_scheduled_emails to only take one user.

No codepath except tests passes in more than one user_profile -- and
doing so is what makes the deduplication necessary.

Simplify the API by making it only take one user_profile id.

(cherry picked from commit ebaafb32f3)
This commit is contained in:
Alex Vandiver
2021-08-14 00:54:46 +00:00
parent 99cc5598ac
commit 7b6cee1164
4 changed files with 8 additions and 24 deletions

View File

@@ -390,23 +390,20 @@ def clear_scheduled_invitation_emails(email: str) -> None:
@transaction.atomic(savepoint=False)
def clear_scheduled_emails(user_ids: List[int], email_type: Optional[int] = None) -> None:
def clear_scheduled_emails(user_id: int, email_type: Optional[int] = None) -> None:
# We need to obtain a FOR UPDATE lock on the selected rows to keep a concurrent
# execution of this function (or something else) from deleting them before we access
# the .users attribute.
items = ScheduledEmail.objects.filter(users__in=user_ids).select_for_update()
items = ScheduledEmail.objects.filter(users__in=[user_id]).select_for_update()
if email_type is not None:
items = items.filter(type=email_type)
deduplicated_items = {}
for item in items:
deduplicated_items[item.id] = item
for item in deduplicated_items.values():
# Now we want a FOR UPDATE lock on the item.users rows
# to prevent a concurrent transaction from mutating them
# simultaneously.
item.users.all().select_for_update()
item.users.remove(*user_ids)
item.users.remove(user_id)
if item.users.all().count() == 0:
# Due to our transaction holding the row lock we have a guarantee
# that the obtained COUNT is accurate, thus we can reliably use it