thumbnail: Resolve a race condition when rendering messages.

Messages are rendered outside of a transaction, for performance
reasons, and then sent inside of one.  This opens thumbnailing up to a
race where the thumbnails have not yet been written when the message
is rendered, but the message has not been sent when thumbnailing
completes, causing `rewrite_thumbnailed_images` to be a no-op and the
message being left with a spinner which never resolves.

Explicitly lock and use he ImageAttachment data inside the
message-sending transaction, to rewrite the message content with the
latest information about the existing thumbnails.

Despite the thumbnailing worker taking a lock on Message rows to
update them, this does not lead to deadlocks -- the INSERT of the
Message rows happens in a transaction, ensuring that either the
message rending blocks the thumbnailing until the Message row is
created, or that the `rewrite_thumbnailed_images` and Message INSERT
waits until thumbnailing is complete (and updated no Message rows).

(cherry picked from commit 6f20c15ae9)
This commit is contained in:
Alex Vandiver
2024-07-31 16:07:10 +00:00
committed by Tim Abbott
parent d34183154f
commit 656ca17e14
5 changed files with 100 additions and 12 deletions

View File

@@ -118,6 +118,7 @@ class MessageRenderingResult:
links_for_preview: set[str]
user_ids_with_alert_words: set[int]
potential_attachment_path_ids: list[str]
thumbnail_spinners: set[str]
@dataclass
@@ -2625,6 +2626,7 @@ def do_convert(
links_for_preview=set(),
user_ids_with_alert_words=set(),
potential_attachment_path_ids=[],
thumbnail_spinners=set(),
)
_md_engine.zulip_message = message
@@ -2683,9 +2685,10 @@ def do_convert(
# Post-process the result with the rendered image previews:
if user_upload_previews is not None:
content_with_thumbnails = rewrite_thumbnailed_images(
content_with_thumbnails, thumbnail_spinners = rewrite_thumbnailed_images(
rendering_result.rendered_content, user_upload_previews
)
rendering_result.thumbnail_spinners = thumbnail_spinners
if content_with_thumbnails is not None:
rendering_result.rendered_content = content_with_thumbnails