scheduled_messages: Send event on commit in edit_scheduled_message.

Earlier, we were using 'send_event' in 'edit_scheduled_messages'
which can lead to a situation, if any db operation is added after
the 'send_event' in future, where we enqueue events but the action
function fails at a later stage.

Events should not be sent until we know we're not rolling back.

Fixes part of #30489.
This commit is contained in:
Prakhar Pratyush 2024-08-08 14:53:08 +05:30 committed by Tim Abbott
parent b0390ce1ee
commit 500fb3d804
1 changed files with 94 additions and 94 deletions

View File

@ -127,6 +127,7 @@ def notify_update_scheduled_message(
send_event_on_commit(user_profile.realm, event, [user_profile.id]) send_event_on_commit(user_profile.realm, event, [user_profile.id])
@transaction.atomic(durable=True)
def edit_scheduled_message( def edit_scheduled_message(
sender: UserProfile, sender: UserProfile,
client: Client, client: Client,
@ -138,110 +139,109 @@ def edit_scheduled_message(
deliver_at: datetime | None, deliver_at: datetime | None,
realm: Realm, realm: Realm,
) -> None: ) -> None:
with transaction.atomic(): scheduled_message_object = access_scheduled_message(sender, scheduled_message_id)
scheduled_message_object = access_scheduled_message(sender, scheduled_message_id)
# Handles the race between us initiating this transaction and user sending us the edit request. # Handles the race between us initiating this transaction and user sending us the edit request.
if scheduled_message_object.delivered is True: if scheduled_message_object.delivered is True:
raise JsonableError(_("Scheduled message was already sent")) raise JsonableError(_("Scheduled message was already sent"))
# If the server failed to send the scheduled message, a new scheduled # If the server failed to send the scheduled message, a new scheduled
# delivery timestamp (`deliver_at`) is required. # delivery timestamp (`deliver_at`) is required.
if scheduled_message_object.failed and deliver_at is None: if scheduled_message_object.failed and deliver_at is None:
raise JsonableError(_("Scheduled delivery time must be in the future.")) raise JsonableError(_("Scheduled delivery time must be in the future."))
# Get existing scheduled message's recipient IDs and recipient_type_name. # Get existing scheduled message's recipient IDs and recipient_type_name.
existing_recipient, existing_recipient_type_name = get_recipient_ids( existing_recipient, existing_recipient_type_name = get_recipient_ids(
scheduled_message_object.recipient, sender.id scheduled_message_object.recipient, sender.id
)
# If any recipient information or message content has been updated,
# we check the message again.
if recipient_type_name is not None or message_to is not None or message_content is not None:
# Update message type if changed.
if recipient_type_name is not None:
updated_recipient_type_name = recipient_type_name
else:
updated_recipient_type_name = existing_recipient_type_name
# Update message recipient if changed.
if message_to is not None:
if updated_recipient_type_name == "stream":
stream_id = extract_stream_id(message_to)
updated_recipient = [stream_id]
else:
updated_recipient = extract_direct_message_recipient_ids(message_to)
else:
updated_recipient = existing_recipient
# Update topic name if changed.
if topic_name is not None:
updated_topic_name = topic_name
else:
# This will be ignored in Addressee.legacy_build if type
# is being changed from stream to direct.
updated_topic_name = scheduled_message_object.topic_name()
# Update message content if changed.
if message_content is not None:
updated_content = message_content
else:
updated_content = scheduled_message_object.content
# Check message again.
addressee = Addressee.legacy_build(
sender, updated_recipient_type_name, updated_recipient, updated_topic_name
)
send_request = check_message(
sender,
client,
addressee,
updated_content,
realm=realm,
forwarder_user_profile=sender,
) )
# If any recipient information or message content has been updated, if recipient_type_name is not None or message_to is not None:
# we check the message again. # User has updated the scheduled message's recipient.
if recipient_type_name is not None or message_to is not None or message_content is not None: scheduled_message_object.recipient = send_request.message.recipient
# Update message type if changed. scheduled_message_object.stream = send_request.stream
if recipient_type_name is not None: # Update the topic based on the new recipient information.
updated_recipient_type_name = recipient_type_name new_topic_name = send_request.message.topic_name()
else: scheduled_message_object.set_topic_name(topic_name=new_topic_name)
updated_recipient_type_name = existing_recipient_type_name elif topic_name is not None and existing_recipient_type_name == "stream":
# User has updated the scheduled message's topic, but not
# the existing recipient information. We ignore topics sent
# for scheduled direct messages.
check_stream_topic(topic_name)
new_topic_name = truncate_topic(topic_name)
scheduled_message_object.set_topic_name(topic_name=new_topic_name)
# Update message recipient if changed. if message_content is not None:
if message_to is not None: # User has updated the scheduled messages's content.
if updated_recipient_type_name == "stream": rendering_result = render_message_markdown(
stream_id = extract_stream_id(message_to) send_request.message, send_request.message.content, send_request.realm
updated_recipient = [stream_id] )
else: scheduled_message_object.content = send_request.message.content
updated_recipient = extract_direct_message_recipient_ids(message_to) scheduled_message_object.rendered_content = rendering_result.rendered_content
else: scheduled_message_object.has_attachment = check_attachment_reference_change(
updated_recipient = existing_recipient scheduled_message_object, rendering_result
)
# Update topic name if changed. if deliver_at is not None:
if topic_name is not None: # User has updated the scheduled message's send timestamp.
updated_topic_name = topic_name scheduled_message_object.scheduled_timestamp = deliver_at
else:
# This will be ignored in Addressee.legacy_build if type
# is being changed from stream to direct.
updated_topic_name = scheduled_message_object.topic_name()
# Update message content if changed. # Update for most recent Client information.
if message_content is not None: scheduled_message_object.sending_client = client
updated_content = message_content
else:
updated_content = scheduled_message_object.content
# Check message again. # If the user is editing a scheduled message that the server tried
addressee = Addressee.legacy_build( # and failed to send, we need to update the `failed` boolean field
sender, updated_recipient_type_name, updated_recipient, updated_topic_name # as well as the associated `failure_message` field.
) if scheduled_message_object.failed:
send_request = check_message( scheduled_message_object.failed = False
sender, scheduled_message_object.failure_message = None
client,
addressee,
updated_content,
realm=realm,
forwarder_user_profile=sender,
)
if recipient_type_name is not None or message_to is not None: scheduled_message_object.save()
# User has updated the scheduled message's recipient.
scheduled_message_object.recipient = send_request.message.recipient
scheduled_message_object.stream = send_request.stream
# Update the topic based on the new recipient information.
new_topic_name = send_request.message.topic_name()
scheduled_message_object.set_topic_name(topic_name=new_topic_name)
elif topic_name is not None and existing_recipient_type_name == "stream":
# User has updated the scheduled message's topic, but not
# the existing recipient information. We ignore topics sent
# for scheduled direct messages.
check_stream_topic(topic_name)
new_topic_name = truncate_topic(topic_name)
scheduled_message_object.set_topic_name(topic_name=new_topic_name)
if message_content is not None:
# User has updated the scheduled messages's content.
rendering_result = render_message_markdown(
send_request.message, send_request.message.content, send_request.realm
)
scheduled_message_object.content = send_request.message.content
scheduled_message_object.rendered_content = rendering_result.rendered_content
scheduled_message_object.has_attachment = check_attachment_reference_change(
scheduled_message_object, rendering_result
)
if deliver_at is not None:
# User has updated the scheduled message's send timestamp.
scheduled_message_object.scheduled_timestamp = deliver_at
# Update for most recent Client information.
scheduled_message_object.sending_client = client
# If the user is editing a scheduled message that the server tried
# and failed to send, we need to update the `failed` boolean field
# as well as the associated `failure_message` field.
if scheduled_message_object.failed:
scheduled_message_object.failed = False
scheduled_message_object.failure_message = None
scheduled_message_object.save()
notify_update_scheduled_message(sender, scheduled_message_object) notify_update_scheduled_message(sender, scheduled_message_object)