fix: wrap IMAP email batch insert in a single transaction
Without a transaction, N individual inserts each re-acquire the SQLite write lock, creating a window where a concurrent sync-log write hits SQLITE_LOCKED. The whole batch then throws, no checkpoint is saved, and the inbox ends up with only the emails that inserted before the failure. Wrapping in one transaction makes the batch atomic and holds the lock for a single commit instead of N. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1ab915d73a
commit
2bd082e90e
@@ -393,37 +393,39 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
'(UID FLAGS ENVELOPE BODYSTRUCTURE RFC822.SIZE)',
|
||||
);
|
||||
var bytes = 0;
|
||||
for (final msg in fetch.messages) {
|
||||
final envelope = msg.envelope;
|
||||
if (envelope == null) {
|
||||
log('IMAP: skipping message with no envelope (uid=${msg.uid}, mailbox=$mailboxPath)');
|
||||
continue;
|
||||
await _db.transaction(() async {
|
||||
for (final msg in fetch.messages) {
|
||||
final envelope = msg.envelope;
|
||||
if (envelope == null) {
|
||||
log('IMAP: skipping message with no envelope (uid=${msg.uid}, mailbox=$mailboxPath)');
|
||||
continue;
|
||||
}
|
||||
final uid = msg.uid;
|
||||
if (uid == null) {
|
||||
log('IMAP: skipping message with no uid (mailbox=$mailboxPath)');
|
||||
continue;
|
||||
}
|
||||
bytes += msg.size ?? 0;
|
||||
final emailId = '${account.id}:$uid';
|
||||
await _db.into(_db.emails).insertOnConflictUpdate(
|
||||
EmailsCompanion.insert(
|
||||
id: emailId,
|
||||
accountId: account.id,
|
||||
mailboxPath: mailboxPath,
|
||||
uid: uid,
|
||||
subject: Value(envelope.subject),
|
||||
sentAt: Value(envelope.date),
|
||||
receivedAt: envelope.date ?? DateTime.now(),
|
||||
fromJson: Value(_encodeAddresses(envelope.from)),
|
||||
toAddresses: Value(_encodeAddresses(envelope.to)),
|
||||
ccJson: Value(_encodeAddresses(envelope.cc)),
|
||||
isSeen: Value(msg.flags?.contains(r'\Seen') ?? false),
|
||||
isFlagged: Value(msg.flags?.contains(r'\Flagged') ?? false),
|
||||
hasAttachment: Value(msg.hasAttachments()),
|
||||
),
|
||||
);
|
||||
}
|
||||
final uid = msg.uid;
|
||||
if (uid == null) {
|
||||
log('IMAP: skipping message with no uid (mailbox=$mailboxPath)');
|
||||
continue;
|
||||
}
|
||||
bytes += msg.size ?? 0;
|
||||
final emailId = '${account.id}:$uid';
|
||||
await _db.into(_db.emails).insertOnConflictUpdate(
|
||||
EmailsCompanion.insert(
|
||||
id: emailId,
|
||||
accountId: account.id,
|
||||
mailboxPath: mailboxPath,
|
||||
uid: uid,
|
||||
subject: Value(envelope.subject),
|
||||
sentAt: Value(envelope.date),
|
||||
receivedAt: envelope.date ?? DateTime.now(),
|
||||
fromJson: Value(_encodeAddresses(envelope.from)),
|
||||
toAddresses: Value(_encodeAddresses(envelope.to)),
|
||||
ccJson: Value(_encodeAddresses(envelope.cc)),
|
||||
isSeen: Value(msg.flags?.contains(r'\Seen') ?? false),
|
||||
isFlagged: Value(msg.flags?.contains(r'\Flagged') ?? false),
|
||||
hasAttachment: Value(msg.hasAttachments()),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user