From 1a9f385e17b9be449fda84a981ba5cc27b5c598a Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Bodas Date: Thu, 3 Jun 2021 10:34:25 +0530 Subject: [PATCH] access_message: Allow selecting message row FOR UPDATE. This is a prep change to start using `SELECT FOR UPDATE` queries when there is a chance of race conditions. --- zerver/lib/message.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/zerver/lib/message.py b/zerver/lib/message.py index f18e267958..8fd5e80716 100644 --- a/zerver/lib/message.py +++ b/zerver/lib/message.py @@ -655,7 +655,9 @@ class ReactionDict: def access_message( - user_profile: UserProfile, message_id: int + user_profile: UserProfile, + message_id: int, + lock_message: bool = False, ) -> Tuple[Message, Optional[UserMessage]]: """You can access a message by ID in our APIs that either: (1) You received or have previously accessed via starring @@ -664,9 +666,21 @@ def access_message( We produce consistent, boring error messages to avoid leaking any information from a security perspective. + + The lock_message parameter should be passed by callers that are + planning to modify the Message object. This will use the SQL + `SELECT FOR UPDATE` feature to ensure that other processes cannot + delete the message during the current transaction, which is + important to prevent rare race conditions. Callers must only + pass lock_message when inside a @transaction.atomic block. """ try: - message = Message.objects.select_related().get(id=message_id) + base_query = Message.objects.select_related() + if lock_message: # nocoverage + # We want to lock only the `Message` row, and not the related fields + # because the `Message` row only has a possibility of races. + base_query = base_query.select_for_update(of=("self",)) + message = base_query.get(id=message_id) except Message.DoesNotExist: raise JsonableError(_("Invalid message(s)"))