From 8f08328329b154fcae1a5c35fe7f46d035a22e27 Mon Sep 17 00:00:00 2001 From: Mark Hahnenberg Date: Fri, 10 Feb 2017 12:40:45 -0800 Subject: [PATCH] [files] Add retry with exp backoff to IMAP connections for file download requests Summary: See diff title Test Plan: Run locally, make sure we backoff Reviewers: juan, spang, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D3886 --- packages/local-sync/src/models/file.js | 82 ++++++++++++++++---------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/packages/local-sync/src/models/file.js b/packages/local-sync/src/models/file.js index 57ab13d27..bf794c381 100644 --- a/packages/local-sync/src/models/file.js +++ b/packages/local-sync/src/models/file.js @@ -26,39 +26,57 @@ module.exports = (sequelize, Sequelize) => { }, instanceMethods: { async fetch({account, db, logger}) { - const settings = Object.assign({}, account.connectionSettings, account.decryptedCredentials()) - const message = await this.getMessage() - const connection = await IMAPConnection.connect({db, settings, logger}) - try { - const folder = await message.getFolder() - const imapBox = await connection.openBox(folder.name) - const stream = await imapBox.fetchMessageStream(message.folderImapUID, { - fetchOptions: { - bodies: this.partId ? [this.partId] : [], - struct: true, - }, - onFetchComplete() { - connection.end() - }, - }) - if (!stream) { - throw new Error(`Unable to fetch binary data for File ${this.id}`) - } - if (/quoted-printable/i.test(this.encoding)) { - return stream.pipe(new QuotedPrintableStreamDecoder({charset: this.charset})) - } else if (/base64/i.test(this.encoding)) { - return stream.pipe(base64.decode()); - } + let numAttempts = 0; + const maxAttempts = 5; + let currentTimeout = 30 * 1000; + const maxTimeout = 2 * 60 * 1000; + while (numAttempts <= maxAttempts) { + const settings = Object.assign({}, + account.connectionSettings, + account.decryptedCredentials(), + {socketTimeout: currentTimeout}) + const message = await this.getMessage() + let connection = null; + try { + connection = await IMAPConnection.connect({db, settings, logger}) + const folder = await message.getFolder() + const imapBox = await connection.openBox(folder.name) + const stream = await imapBox.fetchMessageStream(message.folderImapUID, { + fetchOptions: { + bodies: this.partId ? [this.partId] : [], + struct: true, + }, + onFetchComplete() { + connection.end() + }, + }) + if (!stream) { + throw new Error(`Unable to fetch binary data for File ${this.id}`) + } + if (/quoted-printable/i.test(this.encoding)) { + return stream.pipe(new QuotedPrintableStreamDecoder({charset: this.charset})) + } else if (/base64/i.test(this.encoding)) { + return stream.pipe(base64.decode()); + } - // If there is no encoding, or the encoding is something like - // '7bit', '8bit', or 'binary', just return the raw stream. This - // stream will be written directly to disk. It's then up to the - // user's computer to decide how to interpret the bytes we've - // dumped to disk. - return stream - } catch (err) { - connection.end(); - throw err + // If there is no encoding, or the encoding is something like + // '7bit', '8bit', or 'binary', just return the raw stream. This + // stream will be written directly to disk. It's then up to the + // user's computer to decide how to interpret the bytes we've + // dumped to disk. + return stream + } catch (err) { + if (connection) { + connection.end(); + } + if (numAttempts <= maxAttempts) { + console.error('Error trying to fetch file:', err); + numAttempts += 1; + currentTimeout = Math.min(maxTimeout, currentTimeout * 2); + continue; + } + throw err + } } },