-
Notifications
You must be signed in to change notification settings - Fork 9
2. Refactor Connection & Retry Handling #160
Conversation
59d4991 to
fa2487a
Compare
6c7bc5f to
088d24f
Compare
…stent with bad url parsing errors.
…SAGE_TYPES to CONTENT_TYPES.
088d24f to
ddabc0b
Compare
…rrors consistent.
236eb73 to
abea2d3
Compare
mirpo
left a comment
There was a problem hiding this 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' | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about pino later?
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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}`)
}
})There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if that will ever be updated to capture rejections.
Check this: https://nodejs.org/api/events.html#events_capture_rejections_of_promises
https://github.com/nodejs/node/blob/17ebd464ccdf12a4fb46334ff5d7a71f0f2e70a9/lib/events.js#L165-L206
| maxRetryWait, // max wait time | ||
| Math.round((this.retryCount * 10) ** retryBackoffFactor) | ||
| ) | ||
| if (!timeout) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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.
Connection.disconnect()disables autoconnect. Will only reconnect if asked.connect/disconnectnow roughly equivalent whatensureConnected/ensureDisconnecteddid, methods left for backwards compatibility, could be removed though.Builds on:
Followed by: