[client-sync] Refactor MessageProcessor to be more robust to errors

Summary:
Errors in the MessageProcessor were causing sync to get stuck
occasionally. This diff refactors queueMessageForProcessing and friends
so that they're more robust to errors during promise construction.

Test Plan: Run locally

Reviewers: juan, spang, evan

Reviewed By: evan

Differential Revision: https://phab.nylas.com/D4190
This commit is contained in:
Mark Hahnenberg 2017-03-10 11:07:11 -08:00
parent c971ed03e2
commit 792994d95c

View file

@ -47,14 +47,22 @@ class MessageProcessor {
} }
/** /**
* @returns Promise that resolves when message has been processed * @returns Promise that resolves when message has been processed. This
* This promise will never reject, given that this function is meant to be * promise will never reject. If message processing fails, we will register
* called as a fire and forget operation * the failure in the folder syncState.
* If message processing fails, we will register the failure in the folder
* syncState
*/ */
queueMessageForProcessing({accountId, folderId, imapMessage, struct, desiredParts, throttle = true} = {}) { queueMessageForProcessing({accountId, folderId, imapMessage, struct, desiredParts, throttle = true} = {}) {
return new Promise((resolve) => { return new Promise(async (resolve) => {
let logger;
let folder;
try {
const accountDb = await LocalDatabaseConnector.forShared()
const account = await accountDb.Account.findById(accountId)
const db = await LocalDatabaseConnector.forAccount(accountId);
const {Folder} = db
folder = await Folder.findById(folderId)
logger = global.Logger.forAccount(account)
this._queueLength++ this._queueLength++
this._queue = this._queue.then(async () => { this._queue = this._queue.then(async () => {
if (this._currentChunkSize === 0) { if (this._currentChunkSize === 0) {
@ -62,7 +70,7 @@ class MessageProcessor {
} }
this._currentChunkSize++; this._currentChunkSize++;
await this._processMessage({accountId, folderId, imapMessage, struct, desiredParts}) await this._processMessage({db, accountId, folder, imapMessage, struct, desiredParts, logger})
this._queueLength-- this._queueLength--
// Throttle message processing to meter cpu usage // Throttle message processing to meter cpu usage
@ -79,20 +87,21 @@ class MessageProcessor {
if (this._queueLength === 0) { if (this._queueLength === 0) {
this._queue = Promise.resolve() this._queue = Promise.resolve()
} }
resolve() });
}) } catch (err) {
if (logger && folder) {
await this._onError({imapMessage, desiredParts, folder, err, logger});
} else {
NylasEnv.reportError(err);
}
}
resolve();
}) })
} }
async _processMessage({accountId, folderId, imapMessage, struct, desiredParts}) { async _processMessage({db, accountId, folder, imapMessage, struct, desiredParts, logger}) {
const db = await LocalDatabaseConnector.forAccount(accountId);
const {Message, Folder, Label} = db
const folder = await Folder.findById(folderId)
const accountDb = await LocalDatabaseConnector.forShared()
const account = await accountDb.Account.findById(accountId)
const logger = global.Logger.forAccount(account)
try { try {
const {Message, Folder, Label} = db;
const messageValues = await MessageFactory.parseFromImap(imapMessage, desiredParts, { const messageValues = await MessageFactory.parseFromImap(imapMessage, desiredParts, {
db, db,
folder, folder,
@ -147,6 +156,12 @@ class MessageProcessor {
logger.log(`🔃 ✉️ (${folder.name}) "${messageValues.subject}" - ${messageValues.date}`) logger.log(`🔃 ✉️ (${folder.name}) "${messageValues.subject}" - ${messageValues.date}`)
return processedMessage return processedMessage
} catch (err) { } catch (err) {
await this._onError({imapMessage, desiredParts, folder, err, logger});
return null
}
}
async _onError({imapMessage, desiredParts, folder, err, logger}) {
logger.error(`MessageProcessor: Could not build message`, { logger.error(`MessageProcessor: Could not build message`, {
err, err,
imapMessage, imapMessage,
@ -175,8 +190,6 @@ class MessageProcessor {
mkdirp.sync(outDir); mkdirp.sync(outDir);
fs.writeFileSync(outFile, outJSON); fs.writeFileSync(outFile, outJSON);
} }
return null
}
} }
// Replaces ["<rfc2822messageid>", ...] with [[object Reference], ...] // Replaces ["<rfc2822messageid>", ...] with [[object Reference], ...]