[client-app] Prevent delta streaming connection from retrying too much

Summary:
Before this commit, we would call .close() onError and when the
connection closed. We also closed the connection onError, even though
NylasLongConnection had also closed it, so we ended up calling close a
bunch of times. This would cause us to set a bunch of timeouts to retry
unecessarilly.

This commit makes it so we ensure there's only one retry timeout and
consolidates the logic that calls .close() so we don't call it so many
times unnecessarily

Test Plan: manual

Reviewers: spang, mark, evan

Reviewed By: mark, evan

Differential Revision: https://phab.nylas.com/D4166
This commit is contained in:
Juan Tejada 2017-03-09 13:41:02 -08:00
parent da463c250f
commit d2cd0db335
2 changed files with 88 additions and 74 deletions

View file

@ -98,6 +98,7 @@ class NylasLongConnection {
onError(error) { onError(error) {
this._onError(error) this._onError(error)
this.close()
} }
canStart() { canStart() {
@ -108,6 +109,7 @@ class NylasLongConnection {
if (!this.canStart()) { return this } if (!this.canStart()) { return this }
if (this._req != null) { return this } if (this._req != null) { return this }
try {
const accountToken = this._api.accessTokenForAccountId(this._accountId) const accountToken = this._api.accessTokenForAccountId(this._accountId)
const identityToken = (IdentityStore.identity() || {}).token || '' const identityToken = (IdentityStore.identity() || {}).token || ''
if (!accountToken) { if (!accountToken) {
@ -138,7 +140,6 @@ class NylasLongConnection {
statusCode: responseStream.statusCode, statusCode: responseStream.statusCode,
}) })
this.onError(error) this.onError(error)
this.close()
}) })
return return
} }
@ -146,7 +147,6 @@ class NylasLongConnection {
responseStream.setEncoding('utf8') responseStream.setEncoding('utf8')
responseStream.on('error', (error) => { responseStream.on('error', (error) => {
this.onError(new APIError({error})) this.onError(new APIError({error}))
this.close()
}) })
responseStream.on('close', () => this.close()) responseStream.on('close', () => this.close())
responseStream.on('end', () => this.close()) responseStream.on('end', () => this.close())
@ -165,7 +165,6 @@ class NylasLongConnection {
this._req.setSocketKeepAlive(true) this._req.setSocketKeepAlive(true)
this._req.on('error', (error) => { this._req.on('error', (error) => {
this.onError(new APIError({error})) this.onError(new APIError({error}))
this.close()
}) })
this._req.on('socket', (socket) => { this._req.on('socket', (socket) => {
this.setStatus(Status.Connecting) this.setStatus(Status.Connecting)
@ -175,7 +174,6 @@ class NylasLongConnection {
}) })
socket.on('error', (error) => { socket.on('error', (error) => {
this.onError(new APIError({error})) this.onError(new APIError({error}))
this.close()
}) })
socket.on('close', () => this.close()) socket.on('close', () => this.close())
socket.on('end', () => this.close()) socket.on('end', () => this.close())
@ -184,6 +182,12 @@ class NylasLongConnection {
// See https://github.com/nylas/nylas-mail/pull/2004 // See https://github.com/nylas/nylas-mail/pull/2004
this._req.end() this._req.end()
return this return this
} catch (err) {
// start() should not throw any errors synchronously. Any errors should be
// asynchronously transmitted to the caller via `onError`
setTimeout(() => this.onError(err), 0)
return this
}
} }
closeIfDataStops() { closeIfDataStops() {

View file

@ -19,8 +19,9 @@ class DeltaStreamingConnection {
this._account = account this._account = account
this._state = null this._state = null
this._longConnection = null this._longConnection = null
this._writeStateDebounced = _.debounce(this._writeState, 100) this._retryTimeout = null
this._unsubscribers = [] this._unsubscribers = []
this._writeStateDebounced = _.debounce(this._writeState, 100)
this._backoffScheduler = new ExponentialBackoffScheduler({ this._backoffScheduler = new ExponentialBackoffScheduler({
baseDelay: BASE_RETRY_DELAY, baseDelay: BASE_RETRY_DELAY,
maxDelay: MAX_RETRY_DELAY, maxDelay: MAX_RETRY_DELAY,
@ -67,11 +68,13 @@ class DeltaStreamingConnection {
} }
close() { close() {
this._clearRetryTimeout()
this._disposeListeners() this._disposeListeners()
this._longConnection.close() this._longConnection.close()
} }
end() { end() {
this._clearRetryTimeout()
this._disposeListeners() this._disposeListeners()
this._longConnection.end() this._longConnection.end()
} }
@ -88,6 +91,11 @@ class DeltaStreamingConnection {
this._unsubscribers = [] this._unsubscribers = []
} }
_clearRetryTimeout() {
clearTimeout(this._retryTimeout)
this._retryTimeout = null
}
_onOnlineStatusChanged = () => { _onOnlineStatusChanged = () => {
if (OnlineStatusStore.isOnline()) { if (OnlineStatusStore.isOnline()) {
this.restart() this.restart()
@ -103,7 +111,9 @@ class DeltaStreamingConnection {
this._backoffScheduler.reset() this._backoffScheduler.reset()
} }
if (status === Closed) { if (status === Closed) {
setTimeout(() => this.restart(), this._backoffScheduler.nextDelay()); if (this._retryTimeout) { return }
this._clearRetryTimeout()
this._retryTimeout = setTimeout(() => this.restart(), this._backoffScheduler.nextDelay());
} }
} }
@ -138,9 +148,6 @@ class DeltaStreamingConnection {
if (!NylasAPIRequest.NonReportableStatusCodes.includes(err.statusCode)) { if (!NylasAPIRequest.NonReportableStatusCodes.includes(err.statusCode)) {
NylasEnv.reportError(err) NylasEnv.reportError(err)
} }
this.close()
setTimeout(() => this.restart(), this._backoffScheduler.nextDelay());
} }
_setCursor = (cursor) => { _setCursor = (cursor) => {
@ -160,7 +167,10 @@ class DeltaStreamingConnection {
// Migrate from old storage key // Migrate from old storage key
const oldState = await DatabaseStore.findJSONBlob(`NylasSyncWorker:${this._account.id}`) const oldState = await DatabaseStore.findJSONBlob(`NylasSyncWorker:${this._account.id}`)
if (!oldState) { if (!oldState) {
return {cursor: null, status: null}; return {
cursor: null,
status: null,
};
} }
const {deltaCursors = {}, deltaStatus = {}} = oldState const {deltaCursors = {}, deltaStatus = {}} = oldState