reconnect when no message received in a long time

This commit is contained in:
Kevin Jahns 2019-08-02 00:24:08 +02:00
parent 397164d7bc
commit 953aa592fe
4 changed files with 40 additions and 15 deletions

View File

@ -64,6 +64,7 @@ class WSSharedDoc extends Y.Doc {
* @type {awarenessProtocol.Awareness} * @type {awarenessProtocol.Awareness}
*/ */
this.awareness = new awarenessProtocol.Awareness(this) this.awareness = new awarenessProtocol.Awareness(this)
this.awareness.setLocalState(null)
/** /**
* @param {{ added: Array<number>, updated: Array<number>, removed: Array<number> }} changes * @param {{ added: Array<number>, updated: Array<number>, removed: Array<number> }} changes
* @param {Object | null} conn Origin is the connection that made the change * @param {Object | null} conn Origin is the connection that made the change

18
package-lock.json generated
View File

@ -995,9 +995,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.11", "version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true "dev": true
}, },
"loose-envify": { "loose-envify": {
@ -1699,17 +1699,17 @@
"dev": true "dev": true
}, },
"y-protocols": { "y-protocols": {
"version": "0.0.6", "version": "0.0.7",
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.0.6.tgz", "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.0.7.tgz",
"integrity": "sha512-XgUBKrFesfUYN3wXmVp9Exy7dOUOeX3A56gHNuI1ZNy9N7OdwoBv2TGfbvSH6+YpV1IEvEq7u5v0/je5MwXKJg==", "integrity": "sha512-vimoF3v/H9kMRBKQcbYppObK6FUITPL+huyvbYdPhLiLTSSm5dS7/BT1E/Wyl488CyoPoZTxiOqfu1WphwTe6A==",
"requires": { "requires": {
"lib0": "0.0.5" "lib0": "0.0.5"
} }
}, },
"yjs": { "yjs": {
"version": "13.0.0-83", "version": "13.0.0-94",
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.0.0-83.tgz", "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.0.0-94.tgz",
"integrity": "sha512-8M1X8fZ95odf2VU8BHrKcYr0PeEsx8tspV5svh4oLp8BVcIprbp0J2hzCvJDlOFOlyJVgvNf00UJ4uiyDKmk5A==", "integrity": "sha512-4HETCv/BZ4Mg9rZU+nLZXW8GGp9ugWDA1ImcyIW2hZA2n0YsIgwvROPqWpflwi/l02kfLlvLKF8nEXYk/cOsjQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"lib0": "0.0.5" "lib0": "0.0.5"

View File

@ -40,17 +40,17 @@
] ]
}, },
"dependencies": { "dependencies": {
"y-protocols": "0.0.6", "y-protocols": "0.0.7",
"lib0": "0.0.5" "lib0": "0.0.5"
}, },
"devDependencies": { "devDependencies": {
"rollup": "^1.1.2", "rollup": "^1.1.2",
"rollup-cli": "^1.0.9", "rollup-cli": "^1.0.9",
"standard": "^12.0.1", "standard": "^12.0.1",
"yjs": "13.0.0-83" "yjs": "13.0.0-94"
}, },
"peerDependenies": { "peerDependenies": {
"yjs": ">=13.0.0-83" "yjs": ">=13.0.0-94"
}, },
"optionalDependencies": { "optionalDependencies": {
"ws": "^6.2.1" "ws": "^6.2.1"

View File

@ -10,6 +10,7 @@ Unlike stated in the LICENSE file, it is not necessary to include the copyright
import * as Y from 'yjs' // eslint-disable-line import * as Y from 'yjs' // eslint-disable-line
import * as bc from 'lib0/broadcastchannel.js' import * as bc from 'lib0/broadcastchannel.js'
import * as time from 'lib0/time.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js' import * as decoding from 'lib0/decoding.js'
import * as syncProtocol from 'y-protocols/sync.js' import * as syncProtocol from 'y-protocols/sync.js'
@ -17,13 +18,17 @@ import * as authProtocol from 'y-protocols/auth.js'
import * as awarenessProtocol from 'y-protocols/awareness.js' import * as awarenessProtocol from 'y-protocols/awareness.js'
import * as mutex from 'lib0/mutex.js' import * as mutex from 'lib0/mutex.js'
import { Observable } from 'lib0/observable.js' import { Observable } from 'lib0/observable.js'
import * as math from 'lib0/math.js'
const messageSync = 0 const messageSync = 0
const messageQueryAwareness = 3 const messageQueryAwareness = 3
const messageAwareness = 1 const messageAwareness = 1
const messageAuth = 2 const messageAuth = 2
const reconnectTimeout = 3000 const reconnectTimeoutBase = 1200
const maxReconnectTimeout = 2500
// @todo - this should depend on awareness.outdatedTime
const messageReconnectTimeout = 30000
/** /**
* @param {WebsocketProvider} provider * @param {WebsocketProvider} provider
@ -73,6 +78,7 @@ const setupWS = provider => {
provider.wsconnecting = true provider.wsconnecting = true
provider.wsconnected = false provider.wsconnected = false
websocket.onmessage = event => { websocket.onmessage = event => {
provider.wsLastMessageReceived = time.getUnixTime()
const encoder = readMessage(provider, new Uint8Array(event.data)) const encoder = readMessage(provider, new Uint8Array(event.data))
if (encoding.length(encoder) > 1) { if (encoding.length(encoder) > 1) {
websocket.send(encoding.toUint8Array(encoder)) websocket.send(encoding.toUint8Array(encoder))
@ -81,19 +87,27 @@ const setupWS = provider => {
websocket.onclose = () => { websocket.onclose = () => {
provider.ws = null provider.ws = null
provider.wsconnecting = false provider.wsconnecting = false
provider.wsconnected = false
if (provider.wsconnected) { if (provider.wsconnected) {
provider.wsconnected = false
// update awareness (all users left) // update awareness (all users left)
awarenessProtocol.removeAwarenessStates(provider.awareness, Array.from(provider.awareness.getStates().keys()), provider) awarenessProtocol.removeAwarenessStates(provider.awareness, Array.from(provider.awareness.getStates().keys()), provider)
provider.emit('status', [{ provider.emit('status', [{
status: 'disconnected' status: 'disconnected'
}]) }])
} else {
provider.wsUnsuccessfulReconnects++
} }
setTimeout(setupWS, reconnectTimeout, provider) // Start with no reconnect timeout and increase timeout by
// log10(wsUnsuccessfulReconnects).
// The idea is to increase reconnect timeout slowly and have no reconnect
// timeout at the beginning (log(1) = 0)
setTimeout(setupWS, math.min(Math.log10(provider.wsUnsuccessfulReconnects + 1) * reconnectTimeoutBase, maxReconnectTimeout), provider)
} }
websocket.onopen = () => { websocket.onopen = () => {
provider.wsLastMessageReceived = time.getUnixTime()
provider.wsconnecting = false provider.wsconnecting = false
provider.wsconnected = true provider.wsconnected = true
provider.wsUnsuccessfulReconnects = 0
provider.emit('status', [{ provider.emit('status', [{
status: 'connected' status: 'connected'
}]) }])
@ -167,11 +181,13 @@ export class WebsocketProvider extends Observable {
this.wsconnected = false this.wsconnected = false
this.wsconnecting = false this.wsconnecting = false
this.bcconnected = false this.bcconnected = false
this.wsUnsuccessfulReconnects = 0
this.mux = mutex.createMutex() this.mux = mutex.createMutex()
/** /**
* @type {WebSocket?} * @type {WebSocket?}
*/ */
this.ws = null this.ws = null
this.wsLastMessageReceived = 0
/** /**
* Whether to connect to other peers or not * Whether to connect to other peers or not
* @type {boolean} * @type {boolean}
@ -219,9 +235,17 @@ export class WebsocketProvider extends Observable {
broadcastMessage(this, encoding.toUint8Array(encoder)) broadcastMessage(this, encoding.toUint8Array(encoder))
}) })
awareness.on('change', this._awarenessUpdateHandler) awareness.on('change', this._awarenessUpdateHandler)
this._checkInterval = setInterval(() => {
if (this.wsconnected && messageReconnectTimeout < time.getUnixTime() - this.wsLastMessageReceived) {
// no message received in a long time - not even your own awareness
// updates (which are updated every 15 seconds)
/** @type {WebSocket} */ (this.ws).close()
}
})
this.connect() this.connect()
} }
destroy () { destroy () {
clearInterval(this._checkInterval)
this.disconnect() this.disconnect()
this.awareness.off('change', this._awarenessUpdateHandler) this.awareness.off('change', this._awarenessUpdateHandler)
super.destroy() super.destroy()