mirror of https://github.com/zulip/zulip.git
push_notifications: Lock message while we mark it pending for push.
Deleting a message can race with sending a push notification for it.
b47535d8bb
handled the case where the Message row has gone away --
but in such cases, it is also possible for `access_message` to
succeed, but for the save of `user_message.flags` to fail, because the
UserMessage row has been deleted by then.
Take a lock on the Message row over the accesses of, and updates to,
the relevant UserMessage row. This guarantees that the
message's (non-)existence is consistent across that transaction.
Partial fix for #16502.
This commit is contained in:
parent
12310189ed
commit
1184bdc934
|
@ -1060,43 +1060,46 @@ def handle_push_notification(user_profile_id: int, missed_message: Dict[str, Any
|
||||||
# BUG: Investigate why it's possible to get here.
|
# BUG: Investigate why it's possible to get here.
|
||||||
return # nocoverage
|
return # nocoverage
|
||||||
|
|
||||||
try:
|
with transaction.atomic(savepoint=False):
|
||||||
(message, user_message) = access_message(user_profile, missed_message["message_id"])
|
try:
|
||||||
except JsonableError:
|
(message, user_message) = access_message(
|
||||||
if ArchivedMessage.objects.filter(id=missed_message["message_id"]).exists():
|
user_profile, missed_message["message_id"], lock_message=True
|
||||||
# If the cause is a race with the message being deleted,
|
)
|
||||||
# that's normal and we have no need to log an error.
|
except JsonableError:
|
||||||
return
|
if ArchivedMessage.objects.filter(id=missed_message["message_id"]).exists():
|
||||||
logging.info(
|
# If the cause is a race with the message being deleted,
|
||||||
"Unexpected message access failure handling push notifications: %s %s",
|
# that's normal and we have no need to log an error.
|
||||||
user_profile.id,
|
return
|
||||||
missed_message["message_id"],
|
logging.info(
|
||||||
)
|
"Unexpected message access failure handling push notifications: %s %s",
|
||||||
return
|
user_profile.id,
|
||||||
|
|
||||||
if user_message is not None:
|
|
||||||
# If the user has read the message already, don't push-notify.
|
|
||||||
if user_message.flags.read or user_message.flags.active_mobile_push_notification:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Otherwise, we mark the message as having an active mobile
|
|
||||||
# push notification, so that we can send revocation messages
|
|
||||||
# later.
|
|
||||||
user_message.flags.active_mobile_push_notification = True
|
|
||||||
user_message.save(update_fields=["flags"])
|
|
||||||
else:
|
|
||||||
# Users should only be getting push notifications into this
|
|
||||||
# queue for messages they haven't received if they're
|
|
||||||
# long-term idle; anything else is likely a bug.
|
|
||||||
if not user_profile.long_term_idle:
|
|
||||||
logger.error(
|
|
||||||
"Could not find UserMessage with message_id %s and user_id %s",
|
|
||||||
missed_message["message_id"],
|
missed_message["message_id"],
|
||||||
user_profile_id,
|
|
||||||
exc_info=True,
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if user_message is not None:
|
||||||
|
# If the user has read the message already, don't push-notify.
|
||||||
|
if user_message.flags.read or user_message.flags.active_mobile_push_notification:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Otherwise, we mark the message as having an active mobile
|
||||||
|
# push notification, so that we can send revocation messages
|
||||||
|
# later.
|
||||||
|
user_message.flags.active_mobile_push_notification = True
|
||||||
|
user_message.save(update_fields=["flags"])
|
||||||
|
else:
|
||||||
|
# Users should only be getting push notifications into this
|
||||||
|
# queue for messages they haven't received if they're
|
||||||
|
# long-term idle; anything else is likely a bug.
|
||||||
|
if not user_profile.long_term_idle:
|
||||||
|
logger.error(
|
||||||
|
"Could not find UserMessage with message_id %s and user_id %s",
|
||||||
|
missed_message["message_id"],
|
||||||
|
user_profile_id,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
trigger = missed_message["trigger"]
|
trigger = missed_message["trigger"]
|
||||||
mentioned_user_group_name = None
|
mentioned_user_group_name = None
|
||||||
# mentioned_user_group_id will be None if the user is personally mentioned
|
# mentioned_user_group_id will be None if the user is personally mentioned
|
||||||
|
|
Loading…
Reference in New Issue