Skip to content
This repository was archived by the owner on Dec 21, 2021. It is now read-only.

Conversation

@timoxley
Copy link
Contributor

@timoxley timoxley commented Sep 8, 2020

  • Moved majority of connection & retry handling logic into Connection.
  • Connection is checked and potentially reconnected whenever socket is written to, simplifying everything that writes to socket.
  • Improved reconnection/autoconnect logic, should be more predictable now.
  • Manually calling disconnect() disables autoconnect. Will only reconnect if asked.
  • Cleaned up and improved logging significantly, e.g. now logs the request object/deserialised response rather than hard to read serialized array.
  • Error handling also improved, tried to prevent cases of double logging or even double emitting the same error.
  • connect/disconnect now roughly equivalent what ensureConnected/ensureDisconnected did, methods left for backwards compatibility, could be removed though.

Builds on:

Followed by:

@timoxley timoxley force-pushed the refactor-connection branch from 59d4991 to fa2487a Compare September 8, 2020 19:10
@timoxley timoxley changed the title Refactor connection (WIP) Refactor Connection & Retry Handling Sep 14, 2020
@timoxley timoxley force-pushed the refactor-connection branch 2 times, most recently from 6c7bc5f to 088d24f Compare September 14, 2020 19:13
@timoxley timoxley changed the base branch from master to remove-encryption-code September 14, 2020 19:22
Copy link
Contributor

@mirpo mirpo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@@ -1,169 +1,614 @@
import EventEmitter from 'eventemitter3'
import debugFactory from 'debug'
import Debug from 'debug'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about pino later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep pino looks good to me, that's on my todo

throw new ConnectionError('URL is not defined!')
}
const socket = new WebSocket(url, ...args)
socket.id = uniqueId('socket')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harbu loves => monkey patching

let opened = 0
socket.onopen = () => {
opened = 1
openSockets += opened
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for what do you need openSockets and opened = 1?

Copy link
Contributor Author

@timoxley timoxley Sep 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openSockets -= opened

the opened is for bookkeeping, onclose should only decrement if we actually incremented the counter. Could be a bool I guess but 🤷

I keep track of openSockets for tests, exposed via Connection.getOpen otherwise it's easy to end up opening two connections and only closing one. Yep, this code exists for tests only which is a no-no, but it's just a counter and provides high signal vs noise i.e. should never have any open sockets at end of a test.

   afterEach(async () => {
        await client.disconnect()
        const openSockets = Connection.getOpen()
        if (openSockets !== 0) {
            throw new Error(`sockets not closed: ${openSockets}`)
        }
    })

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is only for tests, it shouldn't be a big problem.

We ended up with a similar sort of counter not being an accurate representation of the actual number of live connections in a broker node. Seemed like the disconnect event didn't always trigger. Apparently if the connection got timed out or smth, the disconnect event wouldn't trigger. Ended up using a counter within the library to print the connection count to logs.

}

if (socket.readyState === WebSocket.CLOSING) {
socket.addEventListener('close', resolve)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember in the network when socket stuck in CONNECTING state. Maybe it's better to add case for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was that using ws directly or using the old client? socket.readyState === WebSocket.OPENING is the connecting state, which is checked above, but does only wait for 'error'/'open' events, assumes these are the only two possible events. Perhaps it should also wait for 'close' in the case of a close without an error before it hits 'open'? and perhaps could do with a timeout. Any idea how to trigger that stuck state?

})
// note if event handler is async and it rejects we're kinda hosed
// until node lands unhandledrejection support
// in eventemitter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using import EventEmitter from 'eventemitter3' is this correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timoxley timoxley changed the title Refactor Connection & Retry Handling 2. Refactor Connection & Retry Handling Sep 21, 2020
maxRetryWait, // max wait time
Math.round((this.retryCount * 10) ** retryBackoffFactor)
)
if (!timeout) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So only log when timeout === 0? When would this occur?

async backoffWait() {
const { retryBackoffFactor = 1.2, maxRetryWait = 10000 } = this.options
return new Promise((resolve) => {
clearTimeout(this._backoffTimeout)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens to those that await the previous (old) promise? Should we reject those?

try {
result = super.emit(event, ...args)
} catch (err) {
super.emit('error', err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if the error that occurs in a listener should be considered an error of the connection?

this.socket.binaryType = 'arraybuffer'
this.socket.events = new EventEmitter()
const reconnectTask = (async () => {
if (this.retryCount > maxRetries) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So does the client stop trying to re-connect after maxRetries?

}

this.debug = this._debug
if (this.initialConnectTask === initialConnectTask) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above in function?

return this.initialConnectTask
}

async _connectOnce() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if connect / reconnect logic could be extracted to be just one function?

* Promisify subscribe/unsubscribe.

* Fail _request call if unsubscribe/subscribe request fails to send.

* Fix missing import in resend test.

* Fix browser tests.

* Clean up unused function in test.

* 4. Refactor Publish (#164)

* Refactor & co-locate publish code.

* Avoid using expensive ethers.Wallet.createRandom() calls in test. e.g. 1000x calls with ethers: 14s, randomBytes: 3.5ms.

* Ensure messageCreationUtil is cleaned up after test.

* Fix non-functional MessageCreationUtil test.

* Swap out receptacle for more flexible mem/p-memoize/quick-lru.

* Convert LoginEndpoints test to async/await.

* Remove calls to ensureConnected/ensureDisconnected in test.

* 5. Message Sequencing – Guarantee sequence follows publish order & Prevent backdated messages silently breaking future publishes (#166)

* Improve authFetch logging.

* Update message sequencer to strictly enforce message order.

* Queue publishes per-stream otherwise can skip forward then back even when messages published in correct sequence.

* Add partial solution to broken backdated messages, at least doesn't break regular sequential publishes.

* Tidy up, add some comments.

* Move publish queue fn into utils.
@timoxley timoxley mentioned this pull request Nov 24, 2020
@timoxley timoxley merged commit 4c4e534 into remove-encryption-code Jan 19, 2021
@timoxley timoxley deleted the refactor-connection branch January 19, 2021 18:34
@timoxley timoxley mentioned this pull request Jan 20, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants