Skip to content

Commit

Permalink
fix(realtime): Connect concurrency
Browse files Browse the repository at this point in the history
Previously, an event could call for a reconnect
while the previous one was waiting before
creating  a new socket. This would create
a two socket in a row.

This change adds a protection against concurrency
when creating a socket.
  • Loading branch information
edas committed Feb 26, 2020
1 parent fc0a94a commit eb04d84
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 2 deletions.
16 changes: 14 additions & 2 deletions packages/cozy-realtime/src/CozyRealtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,19 @@ class CozyRealtime {
*/
async connect() {
logger.info('connecting…')
await this.retryManager.waitBeforeNextAttempt()
this.createSocket()
// avoid multiple concurrent calls to connect, keeps the first one
if (this.waitingForConnect) {
logger.debug('Pending reconnection, skipping reconnect')
return
}
logger.debug('No pending reconnection, will reconnect')
try {
this.waitingForConnect = true
await this.retryManager.waitBeforeNextAttempt()
this.createSocket()
} finally {
this.waitingForConnect = false
}
}

/**
Expand Down Expand Up @@ -618,6 +629,7 @@ class CozyRealtime {
* @private
*/
onOnline() {
logger.info('reconnect because receiving an online event')
this.reconnect()
}

Expand Down
26 changes: 26 additions & 0 deletions packages/cozy-realtime/src/CozyRealtime.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -608,5 +608,31 @@ describe('CozyRealtime', () => {
server.stop(done)
})
})

describe('connection concurrency', () => {
it('should not start two concurrent connections', async done => {
const server = createSocketServer()
const realtime = createRealtime()
const handler = jest.fn()
realtime.subscribe(event, type, undefined, handler)
await realtime.waitForSocketReady()

// makes the reconnect wait
realtime.retryManager.onFailure()
realtime.retryManager.onFailure()
realtime.retryManager.onFailure()
realtime.retryManager.onFailure()
realtime.retryManager.onFailure()
realtime.onOnline()
await sleep(25)

// reconnect before the previous connection finishes to wait
realtime.onOnline()
await realtime.waitForSocketReady()

expect(server.onconnect).toHaveBeenCalledTimes(2)
server.stop(done)
})
})
})
})
5 changes: 5 additions & 0 deletions packages/cozy-realtime/src/RetryManager.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import MicroEE from 'microee'

import logger from './logger'

import {
maxWaitBetweenRetries as defaultMaxWaitBetweenRetries,
baseWaitAfterFirstFailure as defaultBaseWaitAfterFirstFailure,
Expand Down Expand Up @@ -109,6 +111,7 @@ class RetryManager {
* @param {object} error
*/
onFailure(error) {
logger.debug('failure, increase the failure counter of the retry manager')
this.clearSuccessWatcher()
this.increaseFailureCounter()
this.emit('failure', error)
Expand All @@ -128,6 +131,7 @@ class RetryManager {
* Reset failures counters (do not wait before trying to connect next time)
*/
reset() {
logger.debug('reset the retry manager')
this.clearSuccessWatcher()
this.retries = 0
this.wait = 0
Expand Down Expand Up @@ -155,6 +159,7 @@ class RetryManager {
* Wait an amount of time before the next attempt (if needed)
*/
async waitBeforeNextAttempt() {
logger.debug('waitBeforeNextAttempt', this.wait)
if (this.wait) {
await new Promise(resolve => {
global.setTimeout(resolve, this.wait)
Expand Down

0 comments on commit eb04d84

Please sign in to comment.