From 933c265d1ef8da46a91cd2d1acd3ca96463b7439 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Fri, 26 Feb 2021 15:37:34 +0100 Subject: [PATCH 1/2] Fix connection close by server treatment by websocket channels The lack of set channel._open to false when the onclose event is triggered was causing the channel be broken without the other parts of the driver notice. In tbis way, a next iteration trying to get the broken connection will succeded in the try and run the query will result in a eternal pending promise. Mark the channel as closed enable the pool to discard and create a new connection if it needed. --- src/v1/internal/browser/browser-channel.js | 5 ++- test/internal/browser/browser-channel.test.js | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/v1/internal/browser/browser-channel.js b/src/v1/internal/browser/browser-channel.js index b81038c48..ccdab0874 100644 --- a/src/v1/internal/browser/browser-channel.js +++ b/src/v1/internal/browser/browser-channel.js @@ -47,20 +47,21 @@ export default class WebSocketChannel { this._ws = createWebSocket(scheme, config.address) this._ws.binaryType = 'arraybuffer' - let self = this + const self = this // All connection errors are not sent to the error handler // we must also check for dirty close calls this._ws.onclose = function (e) { if (!e.wasClean) { self._handleConnectionError() } + self._open = false } this._ws.onopen = function () { // Connected! Cancel the connection timeout self._clearConnectionTimeout() // Drain all pending messages - let pending = self._pending + const pending = self._pending self._pending = null for (let i = 0; i < pending.length; i++) { self.write(pending[i]) diff --git a/test/internal/browser/browser-channel.test.js b/test/internal/browser/browser-channel.test.js index dfca43768..46ba9ac74 100644 --- a/test/internal/browser/browser-channel.test.js +++ b/test/internal/browser/browser-channel.test.js @@ -24,6 +24,9 @@ import { setTimeoutMock } from '../timers-util' import { ENCRYPTION_OFF, ENCRYPTION_ON } from '../../../src/v1/internal/util' import ServerAddress from '../../../src/v1/internal/server-address' +const WS_OPEN = 1 +const WS_CLOSED = 3 + /* eslint-disable no-global-assign */ describe('WebSocketChannel', () => { let OriginalWebSocket @@ -236,4 +239,43 @@ describe('WebSocketChannel', () => { expect(channel).toBeDefined() expect(warnMessages.length).toEqual(1) } + + it('should set _open to false when connection closes', async () => { + const fakeSetTimeout = setTimeoutMock.install() + try { + // do not execute setTimeout callbacks + fakeSetTimeout.pause() + const address = ServerAddress.fromUrl('bolt://localhost:8989') + const driverConfig = { connectionTimeout: 4242 } + const channelConfig = new ChannelConfig( + address, + driverConfig, + SERVICE_UNAVAILABLE + ) + webSocketChannel = new WebSocketChannel( + channelConfig, + undefined, + createWebSocketFactory(WS_OPEN) + ) + webSocketChannel._ws.close() + expect(webSocketChannel._open).toBe(false) + } finally { + fakeSetTimeout.uninstall() + } + }) + + function createWebSocketFactory (readyState) { + const ws = {} + ws.readyState = readyState + ws.close = () => { + ws.readyState = WS_CLOSED + if (ws.onclose && typeof ws.onclose === 'function') { + ws.onclose({ wasClean: true }) + } + } + return url => { + ws.url = url + return ws + } + } }) From 351222104ca2e403db66c025592a2af07d69ae06 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 1 Mar 2021 17:05:49 +0100 Subject: [PATCH 2/2] fix test --- src/v1/internal/browser/browser-channel.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/v1/internal/browser/browser-channel.js b/src/v1/internal/browser/browser-channel.js index ccdab0874..e72e411ad 100644 --- a/src/v1/internal/browser/browser-channel.js +++ b/src/v1/internal/browser/browser-channel.js @@ -31,7 +31,11 @@ export default class WebSocketChannel { * @param {ChannelConfig} config - configuration for this channel. * @param {function(): string} protocolSupplier - function that detects protocol of the web page. Should only be used in tests. */ - constructor (config, protocolSupplier = detectWebPageProtocol) { + constructor ( + config, + protocolSupplier = detectWebPageProtocol, + socketFactory = createWebSocket + ) { this._open = true this._pending = [] this._error = null @@ -44,7 +48,7 @@ export default class WebSocketChannel { return } - this._ws = createWebSocket(scheme, config.address) + this._ws = socketFactory(scheme, config.address) this._ws.binaryType = 'arraybuffer' const self = this