Skip to content

Commit 0cc1145

Browse files
authored
Fix connection close by server treatment by WebSocket channels (#684)
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.
1 parent fd35bc1 commit 0cc1145

File tree

2 files changed

+49
-2
lines changed

2 files changed

+49
-2
lines changed

src/v1/internal/browser/browser-channel.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ export default class WebSocketChannel {
3131
* @param {ChannelConfig} config - configuration for this channel.
3232
* @param {function(): string} protocolSupplier - function that detects protocol of the web page. Should only be used in tests.
3333
*/
34-
constructor (config, protocolSupplier = detectWebPageProtocol) {
34+
constructor (
35+
config,
36+
protocolSupplier = detectWebPageProtocol,
37+
socketFactory = createWebSocket
38+
) {
3539
this._open = true
3640
this._pending = []
3741
this._error = null
@@ -44,7 +48,7 @@ export default class WebSocketChannel {
4448
return
4549
}
4650

47-
this._ws = createWebSocket(scheme, config.address)
51+
this._ws = socketFactory(scheme, config.address)
4852
this._ws.binaryType = 'arraybuffer'
4953

5054
const self = this
@@ -54,6 +58,7 @@ export default class WebSocketChannel {
5458
if (!e.wasClean) {
5559
self._handleConnectionError()
5660
}
61+
self._open = false
5762
}
5863
this._ws.onopen = function () {
5964
// Connected! Cancel the connection timeout

test/internal/browser/browser-channel.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import { setTimeoutMock } from '../timers-util'
2424
import { ENCRYPTION_OFF, ENCRYPTION_ON } from '../../../src/v1/internal/util'
2525
import ServerAddress from '../../../src/v1/internal/server-address'
2626

27+
const WS_OPEN = 1
28+
const WS_CLOSED = 3
29+
2730
/* eslint-disable no-global-assign */
2831
describe('WebSocketChannel', () => {
2932
let OriginalWebSocket
@@ -236,4 +239,43 @@ describe('WebSocketChannel', () => {
236239
expect(channel).toBeDefined()
237240
expect(warnMessages.length).toEqual(1)
238241
}
242+
243+
it('should set _open to false when connection closes', async () => {
244+
const fakeSetTimeout = setTimeoutMock.install()
245+
try {
246+
// do not execute setTimeout callbacks
247+
fakeSetTimeout.pause()
248+
const address = ServerAddress.fromUrl('bolt://localhost:8989')
249+
const driverConfig = { connectionTimeout: 4242 }
250+
const channelConfig = new ChannelConfig(
251+
address,
252+
driverConfig,
253+
SERVICE_UNAVAILABLE
254+
)
255+
webSocketChannel = new WebSocketChannel(
256+
channelConfig,
257+
undefined,
258+
createWebSocketFactory(WS_OPEN)
259+
)
260+
webSocketChannel._ws.close()
261+
expect(webSocketChannel._open).toBe(false)
262+
} finally {
263+
fakeSetTimeout.uninstall()
264+
}
265+
})
266+
267+
function createWebSocketFactory (readyState) {
268+
const ws = {}
269+
ws.readyState = readyState
270+
ws.close = () => {
271+
ws.readyState = WS_CLOSED
272+
if (ws.onclose && typeof ws.onclose === 'function') {
273+
ws.onclose({ wasClean: true })
274+
}
275+
}
276+
return url => {
277+
ws.url = url
278+
return ws
279+
}
280+
}
239281
})

0 commit comments

Comments
 (0)