fix: set_draft mutating real messages (race)

The issue has been introduced in
cf33db3dcb
(https://github.com/chatmail/core/pull/2887).

This, again, has to do with a race where the draft message
is sent in another Future after `get_draft` but before `sql.execute`.

Related:
- 07fa9c35ee
  (https://github.com/chatmail/core/pull/6052).
- df4fd82140
  (https://github.com/chatmail/core/pull/6061).
This commit is contained in:
WofWca
2026-07-30 18:01:49 +04:00
parent a85ce53680
commit ea01dd2a98

View File

@@ -831,39 +831,34 @@ SELECT id, rfc724_mid, pre_rfc724_mid, timestamp, ?, 1 FROM msgs WHERE chat_id=?
msg.state = MessageState::OutDraft;
msg.chat_id = self;
// if possible, replace existing draft and keep id
if !msg.id.is_special()
&& let Some(old_draft) = self.get_draft(context).await?
&& old_draft.id == msg.id
&& old_draft.chat_id == self
&& old_draft.state == MessageState::OutDraft
{
let affected_rows = context
.sql.execute(
"UPDATE msgs
SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
WHERE id=?7
AND (type <> ?2
OR txt <> ?3
OR txt_normalized <> ?4
OR param <> ?5
OR mime_in_reply_to <> ?6);",
(
time(),
msg.viewtype,
&msg.text,
normalize_text(&msg.text),
msg.param.to_string(),
msg.in_reply_to.as_deref().unwrap_or_default(),
msg.id,
),
).await?;
return Ok(affected_rows > 0);
}
let row_id = context
let (msg_id, changed) = context
.sql
.transaction(|transaction| {
// if possible, replace existing draft and keep id
if !msg.id.is_special() && self.has_draft_with_id(transaction, &msg.id)? {
let affected_rows = transaction.execute(
"UPDATE msgs
SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
WHERE id=?7
AND (type <> ?2
OR txt <> ?3
OR txt_normalized <> ?4
OR param <> ?5
OR mime_in_reply_to <> ?6);",
(
time(),
msg.viewtype,
&msg.text,
normalize_text(&msg.text),
msg.param.to_string(),
msg.in_reply_to.as_deref().unwrap_or_default(),
msg.id,
),
)?;
let changed = affected_rows > 0;
return Ok((msg.id, changed));
}
// Delete existing draft if it exists.
transaction.execute(
"DELETE FROM msgs WHERE chat_id=? AND state=?",
@@ -900,11 +895,13 @@ SELECT id, rfc724_mid, pre_rfc724_mid, timestamp, ?, 1 FROM msgs WHERE chat_id=?
),
)?;
Ok(transaction.last_insert_rowid())
let msg_id = MsgId::new(transaction.last_insert_rowid().try_into()?);
let changed = true;
Ok((msg_id, changed))
})
.await?;
msg.id = MsgId::new(row_id.try_into()?);
Ok(true)
msg.id = msg_id;
Ok(changed)
}
/// Returns number of messages in a chat.